IDEAS SPACE

一步步提升技术 做出你想做的事
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

ReentrantLock源码解析

Posted on 2023-03-12 22:56  LKB_HUGH  阅读(9)  评论(0编辑  收藏  举报

同步

在ReentrantLock的类中,它定义了三个内部类Sync、NonfairSync、FairSync,从名称看,这是ReentrantLock支持公平锁、非公平锁的支持类。

从类的继承关系看,他们都继承了AQS。而,从具体实现看NonfairSync、FairSync都是继承于Sync,而不是直接继承AQS;所以,Sync应该封装了适用于ReentrantLock的同步器通用逻辑。且,有一个属性为Sync。

 

同步的封装表现

锁的表现

 

 

 

ReentrantLock实现Lock类,并实现了对应的方式。以Lock的方式表达了ReentrantLock的锁的语义,并以Lock的通用接口,来让整体使用都是统一的。

当然,整体都是使用了同步器的 Sync 属性的对应方法去具体实现。

 

 

Monitor-Style实现

 

 

 

从注释看,ReentrantLock的Monitor-Style的同步实现是基于实现了AQS的Sync获取Condition对象,基于Condition对象的await与signal的方式来实现的。

 

同步器的Sync深度解析

主要代码总览

从代码上看Sync类,实现了如下的几个方法,nonfairTryAcquire和tryRelease

 

 

 

在NonFairSync类中,只实现了lock与tryAcquire方法,其中tryAcquire直接调用了从Sync继承下来的nonfairTryAcquire方法。release方法则直接继承自Sync类。

在FairSync类中,则实现了lock与tryAcquire方法。release方法则直接继承自Sync类

 

在公平锁与非公平锁的角度

通过公平锁的定义知道,先进来的线程先获取到锁。

从非公平锁的定义知道,获取锁时,刚好没有任何线程竞争过我,则由快的获取到。

通过上面的总览知道,NonFairLock继承自Sync的nonfairTryAcquire是非公平锁的具体实现。

而,FairLock的公平锁具体实现tryAcquire方法。

对比时,我们只看下面俩段。

 

 

从实现上看,其步骤都会获取锁的状态,为0时,则是可以获取的时候。进入到线程获取锁的判断。

非公平锁的实现:

步骤一:先通过compareAndSetState这个CAS动作直接修改锁的状态(由CAS已知其保证了多线程竞争时的原子性,能保证只有一个线程可以获取到)

步骤二:在通过setExclusiveOwnerThread方法,设置了该线程为最终得主。

 

公平锁实现:

步骤一:判断队列中是否还有前驱,如果有则失败,没有则进入步骤二

步骤二:跟非公平锁一样的实现。

 

对比非公平与公平锁的实现,可以知道ReentrantLock的公平锁与非公平锁的实现,就是判断队列中是否有前驱,采取竞争锁。

 

其实:Doug Lea的《The java.util.concurrent Synchronizer Framework》文章中,有一句这样的话,就说明了其实现。

 

【这篇文章的解读,以及深刻的解析,会在后面完成】
 

在可重入实现的角度

在可重入的角度,从可重入的定义知道,一个线程可以一直获取已经属于它的锁。

那么,只要做的俩点,就能实现这个定义

  1. 获取锁的线程,判断是否是已经获取到锁的线程
  2. 修改获取到锁的状态,且能在释放锁的时候,正确维护这个状态,重新设置为可以获取锁的状态

从上面公平锁与非公平锁的实现中,我们知道了有一个方法setExclusiveOwnerThread可以设置现在那个线程正拥有锁。所以解决了问题1。

现在,我们来看下面几个代码片段,如何维护锁的状态,正确表示锁的重入,以及释放。

 

重入状态变更

 

释放状态变更

 

 

从重入状态变更中看,无论是公平锁还是非公平锁,都实现了一块代码

步骤一:判断是否为当前线程获取到锁,如果是进入步骤二

步骤二:将当前的状态值,再加上一次acquires的值。并设置为新的状态值

 

从释放状态变更中看,由于NonFairSync与FairSync都是继承自Sync的tryRealase方法。所以,释放用的就是相同的代码。

步骤一:将状态值,减少一个release值

步骤二:判断是否是为当前线程获取到锁,如果是进入步骤三

步骤三:如果状态值已经为0,释放锁。若不是,则将设置新的状态值(步骤一所得)。

 

从上面的分析可以知道,重入是加上一个值,释放是减少一个值。如果,我们tryAcquire与tryRelease时用的是同一个字的话,那么最终执行相同个数的tryAcquire与tryRelease,可以保证锁的状态重新回到可以获取的状态(值为0)。从下方ReentrantLock的俩个实现tryLock与unlock都是封装了tryAcquire与tryRelease中的实现,而其acquires与arg值都为1(这里之后再深挖进去,内部使用了tryRelease方法),验证了我们的想法。

 

故,这里解决了问题2.

所以,这就是ReentrantLock可重入的实现。其实这里跟Synchronized关键字的实现思路基本一致,而Synchronized的实现是基于MonitorEnter与MonitorExit指令。

 

Monitor-Style的实现