ReentrantLock 源码解读
Posted 小毕超
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ReentrantLock 源码解读相关的知识,希望对你有一定的参考价值。
一、ReentrantLock
ReentrantLock
是 java
JUC
中的一个可重入锁,在上篇文章讲解 AQS
源码的时候提到 ReentrantLock
锁是基于 AQS
实现的,那是如何使用的 AQS
呢,本篇文章一起带大家看下 ReentrantLock
的源码。
在 AQS
中,如果需要使用AQS
的特征则需要子类根据使用的场景,重写下面方法,
//查询是否正在独占资源,condition会使用
boolean isHeldExclusively()
//独占模式,尝试获取资源,成功则返回true,失败则返回false
boolean tryAcquire(int arg)
//独占模式,尝试释放资源,成功则返回true,失败则返回false
boolean tryRelease(int arg)
//共享模式,尝试获取资源,如果返回负数表示失败,否则表示成功。
int tryAcquireShared(int arg)
//共享模式,尝试释放资源,成功则返回true,失败则返回false。
boolean tryReleaseShared(int arg)
由于这里 ReentrantLock
锁的特性,所以下面我们只需关注独占模式下的几个方法即可。
说明:本文中关于 AQS
中的方法没有做过多的解释,不了解的小伙伴可以看下这篇对 AQS
源码分析的文章,和当前文章在同一专栏:
下面一起开始 ReentrantLock
源码的分析:
二、ReentrantLock 的 Sync、FairSync、NonfairSync
2.1 Sync、FairSync、NonfairSync
在声明 ReentrantLock
锁时,有两种方式,一种是无参构造函数,一种则需要指定一个 fair
参数:
new ReentrantLock();
new ReentrantLock(false);
当使用无参构造函数声明时,则是创建了一个 NonfairSync
对象:
通过有参的构造函数,则根据传入的 fair
可以选择创建一个 FairSync
对象:
其实这里也不难理解 NonfairSync
和 FairSync
其实就是ReentrantLock
锁中的非公平锁和公平锁两种类型。
点到这两个类中,可以看到都继承自 Sync
类:
而 Sync
类,则继承了 AQS
:
到这里了,我们寻找几个关键的方法,在AQS
中独占模式下,两大关键的方法是交由子类进行实现的,分别是 tryAcquire
尝试获取资源,和 tryRelease
尝试释放资源。
首先来看 tryAcquire
尝试获取资源:
通过 Sync
类的实现源码发现并没有重写 tryAcquire
方法,那该方法肯定在下面的子类FairSync
和 NonfairSync
,分别看下源码确实存在重写的方法:
2.2 NonfairSync 下的 tryAcquire
首先看下 NonfairSync
的 tryAcquire
实现逻辑,可以看到又调用了 nonfairTryAcquire
就是 Sync
类中的 nonfairTryAcquire
,从命名上可以分析出就是非公平锁的尝试获取资源,直观就是非公平锁下获取锁操作:
进入到 Sync
类中的 nonfairTryAcquire
中,可以看到首先获取到 AQS
中的共享资源 state
,如果 state
等于 0
,则将 state
的值修改为 acquires
(默认为1
,下面会分析到),并设置AQS
的独占线程为当前线程,并返回 true
,说白了不就是获取到锁了吗,那就可以理解为 state
等于 0
即是无锁的状态,下面将 state
的值修改为 acquires
就是获取到锁了,改变资源的状态:
接着如果 state
的值不是 0
,则当前锁已经被别的线程持有了,这里又判断了下,如果持有锁的线程正好是当前的线程,那不就是锁的重入吗,这种情况下可以直接获得锁,不过这里为了记录重入的次数,对 state
共享资源进行了 + acquires
操作,其实就是 +1
操作。
如果都没有成功,那此时则获取锁失败,返回 false
2.3 FairSync下的 tryAcquire
在 FairSync
类下的 tryAcquire
方法中,和前面 NonfairSync
类似,但不同的是,在获取到锁时,也就是拿到 state
等于 0
,进行修改资源时,多了步 hasQueuedPredecessors
的判断:
下面可以进到 hasQueuedPredecessors
的方法中,可以看到是由 AQS
提供的方法,主要就是判断当前节点线程的前面是否还有等待的线程,因为 FairSync
实现的是公平锁的原则,如果当前线程前面还有等待线程,则获取锁资源也轮不到自个,让前面的老大先来:
hasQueuedPredecessors
方法理解后,其余的逻辑则和 NonfairSync
中的一致了。
2.4 tryRelease
到这里已经了解到了tryAcquire
尝试获取资源的逻辑,上面提到了两个重要方法,还有一个 tryRelease
没有分析逻辑,还是首先看 Sync
类中是否有重写该方法:
通过源码可以看到,在 Sync
类中就已经对 tryRelease
进行了重写,而 NonfairSync
和 FairSync
中都没有重写该方法,那释放资源就是走的 Sync
类下的 tryRelease
方法:
在该方法中,可以看到首先还是获取到了 AQS
中的 state
共享资源,然后对该资源进行 - releases
(默认releases
为1
,下面会提到 )操作,其实就是 -1
操作:
接着判断了下,如果当前线程不是持有锁线程,就抛出异常,也好理解,没有持有锁的线程跑过来释放锁,那肯定有问题了呀。
接着再进行判断 state
是不是等于 0
,上面讲到在锁重入的情况下,记录重入的次数是对 state
进行 +1
操作,而这边又对 state
进行 -1
操作,如果减到最后 state
有成了最初的 0
,那不就是重入的锁和当前持有的锁都释放完了吗,这个时候就可以将持有锁的线程置为空了,并修改最新的 state
:
看到这里就会发现获取锁和释放锁,无非就是对 AQS
中的共享资源进行操作。理解了这两大核心的方法后,下面就可以看如何运用在 ReentrantLock
中的了。
三、lock.lock()
在 ReentrantLock
中,需要获取锁时,直接使用 lock.lock()
即可,那 lock.lock()
到底做了什么呢,点到该方法中,可以看到是调用的 Sync
的 lock
方法,而 Sync
中的lock
方法是抽象方法,具体实现肯定在子类的 NonfairSync、 FairSync
中。
3.1 NonfairSync.lock()
首先看点 NonfairSync
非公平锁中的 lock
方法,直接进行了将 AQS
中的共享资源 state
由 0
改为 1
,如果修改成功,根据上面分析的结论不就是获取锁成功了吗,可以将AQS
中的独占线程设为自己了。但是如果其他线程修改成功了,这里使用 CAS
就会修改失败,因此就会进到 acquire
方法,注意这里传递的参数默认就是 1
,对应着前面括号中的说明:
而 acquire
方法,就是 AQS
中的独占模式获取同步资源的逻辑,会调用当前方法的 tryAcquire
尝试获取资源,如果获取不到,则加入到 AQS
的阻塞队列并阻塞挂起线程。
关于acquire
方法的源码解读可以参考文章开始的链接中对 AQS
源码的解读。
3.2 FairSync.lock()
在 FairSync
公平锁中,由于需要遵循先进先出的原则,这里没有直接已粗暴的形式对 state
进行修改,而是直接调用了 AQS
中的 acquire
方法,而 acquire
方法又会调用当前类的 tryAcquire
获取资源。
但在当前类的 tryAcquire
方法中,如果获取到了资源,会接着进行判断当前线程的前面是否还有等待的线程,如果有则让出来让别人获取资源,因此就遵循了公平锁的原则,注意这里传递的参数默认就是 1
,同样对应着前面括号中的说明:
同样 tryAcquire
尝试获取资源,如果获取不到,则加入到 AQS
的阻塞队列并阻塞挂起线程。
四、lock.unlock()
上面了解到了 lock
的逻辑,既然上锁了肯定需要解锁,下面点到 unlock()
方法中,可以看到直接使用了 Sync
的 release
方法释放资源,其实是 AQS
中的 release
方法,注意这里传递的参数默认就是 1
,同样对应着前面括号中的说明:
在 AQS
的release
方法中,首先会调用 Sync
类的 tryRelease
释放资源,然后对已阻塞的线程进行唤醒:
关于release
方法的源码解读可以参考文章开始的链接中对 AQS
源码的解读。
五、总结
通过阅读 ReentrantLock
的源码可以发现,大量依赖于 AQS
中提供的方法,所以在阅读前一定要理解下 AQS
的作用和功能。
以上是关于ReentrantLock 源码解读的主要内容,如果未能解决你的问题,请参考以下文章