Java并发编程 ReentrantLock 源码分析
ReentrantLock 一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。 这个类主要基于AQS(AbstractOwnableSynchronizer)封装的 公平与非公平锁。
所谓公平锁就是指 在多个线程的争用下,这些锁倾向于将访问权授予等待时间最长的线程,换句话说也就是先被锁定的线程首先获得锁。 非公平锁正好相反,解锁时没有固定顺序。
让我们边分析源代码边学习如何使用该类
先来看一下构造参数,默认是非公平锁。
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
} /**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
NonfairSync是非公平锁,我们先来看非公平锁,是一个内部类继承了Sync。
/**
* Sync object for non-fair locks
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L; /**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
} protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
我们看到它继承了Sync,我们接着看这个类的源码。
     abstract static class Sync extends AbstractQueuedSynchronizer {
         private static final long serialVersionUID = -5179523762034025860L;
         abstract void lock();
         final boolean nonfairTryAcquire(int acquires) {
             final Thread current = Thread.currentThread();
             int c = getState();
             if (c == 0) {
                 if (compareAndSetState(0, acquires)) {
                     setExclusiveOwnerThread(current);
                     return true;
                 }
             }
             else if (current == getExclusiveOwnerThread()) {
                 int nextc = c + acquires;
                 if (nextc < 0) // overflow
                     throw new Error("Maximum lock count exceeded");
                 setState(nextc);
                 return true;
             }
             return false;
         }
         protected final boolean tryRelease(int releases) {
             int c = getState() - releases;
             if (Thread.currentThread() != getExclusiveOwnerThread())
                 throw new IllegalMonitorStateException();
             boolean free = false;
             if (c == 0) {
                 free = true;
                 setExclusiveOwnerThread(null);
             }
             setState(c);
             return free;
         }
     }
Sync这个类与AbstractQueuedSynchronizer 一起完成了锁的逻辑,现在我们开始从头分析一个线程如何获取锁,以及获取不到锁时如何被阻塞。当用户调用lock方法获取锁的时候,首先会先通过compareAndSetState(NonfairSync第11行)来设置锁定状态,如果原先状态为0,则说明目前没有线程持有锁,那么设置状态为1,并且设置当前线程是当前拥有独占访问的线程(setExclusiveOwnerThread),那么另外一种情况就是compareAndSetState方法返回false,也就是说之前已经有线程持有锁,那么就会执行acquire方法(NonfairSync第15行),这个方法是AbstractQueuedSynchronizer里面的方法。
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
首先调用了tryAcquire方法,这个类在子类当中实现,也就是上面NonfairSync第18行,实际上他调用了 nonfairTryAcquire,这个方法分两步,首先在判断一下状态(state)是否等于0,也就是重新尝试获取所,如果获取到锁则改变状态compareAndSetState 然后设置当前线程是当前拥有独占访问的线程(setExclusiveOwnerThread),跟上面讲到的一样。
如果重新尝试获取所失败,则判断是不是当前线程重复加锁,如果是的话就把状态进行增加。
如果上面都不是就返回FALSE, 如果返回FALSE 那么 acquire(int arg)方法的acquireQueued就会执行,这个方法会把不能获取锁的线程形成一个CHL队列保存起来,然后把线程阻塞。上面就基本讲完了
线程如何获取锁, 获取到锁就把状态设置成1。
如果是持有锁的线程继续调用 LOCK方法,那就把状态进行叠加。
如果获取不到锁,那么AbstractQueuedSynchronizer 会把线程以一个CHL队列的形式保存起来,然后设置线程阻塞,等待释放。
然后就是释放锁的操作
     public void unlock() {
         sync.release(1);
     }
这个release方法又是AbstractQueuedSynchronizer 里面提供的方法。
     public final boolean release(int arg) {
         if (tryRelease(arg)) {
             Node h = head;
             if (h != null && h.waitStatus != 0)
                 unparkSuccessor(h);
             return true;
         }
         return false;
     }
首先在第2行先调用tryRelease尝试释放锁,这个方法是在ReentrantLock的内部类Sync当中重写的,释放成功会返回TRUE。然后调用unparkSuccessor来释放阻塞队列当中的线程,然后被唤醒的线程会继续获取锁,如此反复。
最后我们再来看一下说明是公平锁FairSync。
     static final class FairSync extends Sync {
         private static final long serialVersionUID = -3000897897090466540L;
         final void lock() {
             acquire(1);
         }
         /**
          * Fair version of tryAcquire.  Don't grant access unless
          * recursive call or no waiters or is first.
          */
         protected final boolean tryAcquire(int acquires) {
             final Thread current = Thread.currentThread();
             int c = getState();
             if (c == 0) {
                 if (!hasQueuedPredecessors() &&
                     compareAndSetState(0, acquires)) {
                     setExclusiveOwnerThread(current);
                     return true;
                 }
             }
             else if (current == getExclusiveOwnerThread()) {
                 int nextc = c + acquires;
                 if (nextc < 0)
                     throw new Error("Maximum lock count exceeded");
                 setState(nextc);
                 return true;
             }
             return false;
         }
     }
仔细观察你会发现它与非公平锁唯一的区别就是在tryAcquire这里方法里面。上面已经用红色标记上了,主要是多了这么一个条件,就体现了锁的公平性。
当一个线程尝试获取锁时,那么先会判断当前有没有已经等待获取锁的线程队列,如果的话,按照公平原则,那么当前线程就会被加入阻塞队列的尾巴,如果是非公平锁,那么则不会判断。
这个类的核心原理基本介绍完了,其实主要核心的东西是在AbstractQueuedSynchronizer这个类里。
这里给大家提供一个关于AbstractQueuedSynchronizer的源码分析的文章:
http://ifeve.com/introduce-abstractqueuedsynchronizer/ 大家可以结合着一起看一下。
Java并发编程 ReentrantLock 源码分析的更多相关文章
- Java并发编程-ReentrantLock源码分析
		一.前言 在分析了 AbstractQueuedSynchronier 源码后,接着分析ReentrantLock源码,其实在 AbstractQueuedSynchronizer 的分析中,已经提到 ... 
- Java并发编程-AbstractQueuedSynchronizer源码分析
		简介 提供了一个基于FIFO队列,可以用于构建锁或者其他相关同步装置的基础框架.该同步器(以下简称同步器)利用了一个int来表示状态,期望它能够成为实现大部分同步需求的基础.使用的方法是继承,子类通过 ... 
- Java并发编程  LockSupport源码分析
		这个类比较简单,是一个静态类,不需要实例化直接使用,底层是通过java未开源的Unsafe直接调用底层操作系统来完成对线程的阻塞. package java.util.concurrent.locks ... 
- java 并发编程——Thread 源码重新学习
		Java 并发编程系列文章 Java 并发基础——线程安全性 Java 并发编程——Callable+Future+FutureTask java 并发编程——Thread 源码重新学习 java并发 ... 
- Java异步编程——深入源码分析FutureTask
		Java的异步编程是一项非常常用的多线程技术. 之前通过源码详细分析了ThreadPoolExecutor<你真的懂ThreadPoolExecutor线程池技术吗?看了源码你会有全新的认识&g ... 
- Java并发-ConcurrentModificationException原因源码分析与解决办法
		一.异常原因与异常源码分析 对集合(List.Set.Map)迭代时对其进行修改就会出现java.util.ConcurrentModificationException异常.这里以ArrayList ... 
- 并发编程—— FutureTask 源码分析
		1. 前言 当我们在 Java 中使用异步编程的时候,大部分时候,我们都会使用 Future,并且使用线程池的 submit 方法提交一个 Callable 对象.然后调用 Future 的 get ... 
- 并发编程 —— Timer 源码分析
		前言 在平时的开发中,肯定需要使用定时任务,而 Java 1.3 版本提供了一个 java.util.Timer 定时任务类.今天一起来看看这个类. 1.API 介绍 Timer 相关的有 3 个类: ... 
- Java并发编程之ReentrantLock源码分析
		ReentrantLock介绍 从JDK1.5之前,我们都是使用synchronized关键字来对代码块加锁,在JDK1.5引入了ReentrantLock锁.synchronized关键字性能比Re ... 
随机推荐
- POJ     1129  Channel Allocation(DFS)
			Channel Allocation Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 13173 Accepted: 67 ... 
- 实战Apache+Tomcat集群和负载均衡
			实战Apache+Tomcat集群和负载均衡 目录 1. 什么是J2EE集群... 3 1.1. 序言... 3 1.2. 基本术语... 3 伸缩性(Scalability): ... 
- mysql数据库,创建只读用户
			数据库当前只有一个root用户,需要创建一个只读帐户给其他使用,因使用者是使用数据库管理工具在其他主机访问,所以还要开户远程访问权限,操作步骤如下. 1. 使用现有的root用户登录到Mysql. m ... 
- Android Activity标签属性
			Android Activity标签属性 Activity 是 Android 系统四大应用组件之一,用户可与 Activity 提供的屏幕进行交互,以执行拨打电话.拍摄照片.发送电子邮件等操作开发者 ... 
- Vivado抓取信号
			作者:桂. 时间:2018-05-03 21:16:03 链接:www.cnblogs.com/xingshansi/p/8987608.html 前言 FPGA调试需要抓取特定信号,一个直观的思路 ... 
- django-mptt 树形结构的实现
			觉得这篇博客可以参考:https://blog.csdn.net/abc_1234d/article/details/78360006 
- CSS边框闪烁呼吸样式
			<html> <body> <head> .arrow_box{animation: glow 800ms ease-out infinite alternate; ... 
- Android Eclipse Libs 的 jar 源码查看 (或者新版本ADT无法查看jar的源码)
			问题背景:在使用比较新的ADT的时候,无法导入Jar中的源码包查看源码.只好自己打开压缩包,实在恼火.在半年前,只好这样. 问题解决方案:我就以 " android-support-v4.j ... 
- [Windows Azure] Load Balancing Virtual Machines
			Load Balancing Virtual Machines All virtual machines that you create in Windows Azure can automatica ... 
- (原创)C++11改进我们的程序之简化我们的程序(五)
			这次要讲的是:c++11中的bind和function std::function 它是函数.函数对象.函数指针.和成员函数的包装器,可以容纳任何类型的函数对象,函数指针,引用函数,成员函数的指针.以 ... 
