ReentrantLock获取锁释放锁源码浅析

Posted lay2017

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ReentrantLock获取锁释放锁源码浅析相关的知识,希望对你有一定的参考价值。

JUC包下的ReentrantLock是基于Aqs模板实现的,它区分公平锁和非公平锁,内部实现了两个同步器,本文关注非公平锁部分。

伪代码

我们先看两个伪代码:

1、获取锁

 1 if(获取锁成功) 
 2     return
 3  else 
 4     加入等待队列
 5     for(死循环) 
 6         if (获取到锁) 
 7             return
 8          
 9         阻塞线程
10     
11 

我们看到,如果一次获取成功则结束,如果没有获取成功将进入循环中,并且当前线程阻塞直到被唤醒并且获取到锁才结束。

2、释放锁

1 if(释放锁) 
2     唤醒等待队列中阻塞的首个线程
3 

释放锁的逻辑比较简单,如果当前锁释放了,唤醒下一个。

DEMO

通过伪代码了解了ReentrantLock基本原理以后,我们从一个DEMO入手,看看它的源码实现

技术图片

获取锁

DEMO中初始化了一个ReentrantLock实例,并且调用了lock()方法,在结束的时候调用了unlock()方法。我们先进入lock()方法看看互斥锁是怎么加锁的

技术图片

ReentrantLock内部调用了一个sync对象的lock()方法,我们看看sync是什么

技术图片

这里Sync是一个继承了Aqs模板的同步器抽象类,Sync有两个实现类,一个是非公平锁实现

技术图片

一个是公平锁实现

技术图片

好吧,简单来说就是ReentrantLock在初始化的时候构造了一个NonfairSync或者FairSync对象。并且在调用lock()方法的时候,其实是去调用这个Sync实例对象的lock()方法而实现的加锁操作。那么我们就看看Sync实例是怎么实现加锁的,进入NonfairSync的lock方法。

技术图片

lock()方法先取执行了一个CAS操作,把一个state变量从0设置为1,state变量是声明在Aqs模板的成员变量

技术图片

ReentrantLock在设计的时候认为state > 0的时候表示锁被持有,state = 0的时候表示锁没有持有者。所以这里的cas机制做了一次尝试获取锁的操作。如果获取成功了,那么就将当前线程设置为锁的持有者。如果设置失败了,意味着当前有持有者,那么调用Aqs的acquire方法去获取,我们看看Aqs的acquire方法做了什么

技术图片

acquire方法,先尝试tryAcquire获取一次锁,如果获取成功则结束,我们看看NonfairSync怎么实现tryAcquire的

技术图片

逻辑也很简单,采用CAS做了一次设置,如果是重入的话,那么state + 1。

如果失败先调用addWaiter方法,再调用acquireQueued方法。我们先看看addWaiter做了什么

技术图片

我们看到,Thread被包装成了一个Node节点,Aqs中是使用链表的方式来实现等待队列的,如

技术图片

总之,当前节点将会被添加到队列的尾巴,如果没有添加成功调用enq()方法,我们看看enq方法

技术图片

enq方法其实就是通过自旋操作保证添加到队列的尾巴,所以addWaiter的核心就是没有获取到锁的线程被加入到队列中。我们再回到acquire方法中看看acquireQueued在addWaiter做了什么

技术图片

acquireQueued方法的逻辑比较简单,其实就是在一个for循环中自旋,如果轮到当前节点了,那么就跳出循环。否则被阻塞,直接被唤醒重新自旋。

以上获取锁的源码,与一开始的伪代码逻辑是一样的,如果获取到锁那么state设置为1,重入再+1。如果获取锁失败,那么Thread被包装成Node加入到链表的最尾巴。并且在for循环里面判断是否轮到当前Node获取锁了。

释放锁

 下面再看看ReentrantLock的unlock方法

技术图片

跟lock一样,直接调用了Sync同步器的release方法

技术图片

释放的逻辑比较简单,先调用了一次tryRelease,如果成功的话将等待队列里面的下个线程唤醒就结束了。我们看看NonfairSync是怎么实现tryRelease的吧

技术图片

其实就是把state扣减,如果等于0的话,意味着完全释放锁,那么把独占锁的线程设置为null即可。

总结

从ReentrantLock中,我们看到它基于Aqs实现了Sync同步器,而同步器基本上只是实现了一个tryAcquire方法和tryRelease方法,他们分别会被Aqs中的acquire方法和release方法调用。其它的逻辑,比如入队出队全都被Aqs自己实现了。同时我们还看到了Aqs中多出使用CAS机制来控制数据的更改。另外Aqs提供一个state变量给我们,我们至于怎么使用它需要我们自己设计,比如ReentrantLock将state > 0设计为获取锁,state = 0设计为没有任何获取锁的线程。

 

以上是关于ReentrantLock获取锁释放锁源码浅析的主要内容,如果未能解决你的问题,请参考以下文章

浅析JUC-CountDownLatch

浅析JUC-CountDownLatch

Java - "JUC" ReentrantLock释放锁

图解源码之java锁的获取和释放(AQS)篇

ReentrantLock源码简析

Java Review - 并发编程_独占锁ReentrantLock原理&源码剖析