ReentrantLock

发布时间 2023-08-03 10:24:13作者: 追风fc

ReentrantLock重入锁可以显示的加锁释放锁,且可以配合Condition指定阻塞和唤醒线程,相比synchronized更加灵活。并且已api接口形式提供给开发,我们可以直接阅读源码,看下底层是如何进行锁的实现。

一. ReentrantLock

1.1 成员变量和构造方法

 

Sync是ReentrantLock的静态内部列,其实现了AbstractQueuedSynchronizer(抽象队列同步器,简称AQS,很重要)

NonfairSync和FairSync都继承了Sync,分别为非公平锁和公平锁的实现。

无参构造方法为非公平锁;有参构造可以自定位为公平锁还是非公平锁。

1.2 方法(重点介绍lock,unlock)


1.2.1 lock的实现

1.2.1.1 非公平锁实现

(1)假设现在有场景:thread1来加锁,执行业务流程,释放锁的         


lock()加锁


unlock()释放锁


AQS里的release方法,该方法是一个模板方法,子类都会调用到该方法,但是里面的抽象方法tryRelease子类有各自的实现,下面看Syn里的实现


判断AQS里的state值(private volatile int state),该变量用volatile修饰,主要是为了多个线程之间的通信,并且可以防止指令重排序。

加锁的时候通过cas操作已经将state设为1,这里减去1后要判断下是否为0(因为ReentrantLock是可重入锁,后续场景会讲到),如果为0,则将当前锁用者设为null,更新state,释放锁成功。

 

(2)假设现在有场景:thread1,thread2,thread3依次来加锁,执行业务流程,释放锁的。thread1获取到锁,并且一直未释放锁。

那么thread2CAS操作失败,会执行到acquire()方法

AQS里的acquire方法同release方法一样,该方法是个模板方法,给子类调用,里面的抽象方法tryAcquire子类有各自的实现



 


先获取state,如果为0就尝试获取锁;如果非0,就判断当前拥有锁者是否为自己,如果为自己就将state加1,这也就是重入锁的实现。



在我们的场景里,这里会返回false,那么就继续执行AQS里的acquireQueued方法,我们先看addWaiter方法

新建一个包含当前线程的Node,加入AQS的线程队列中


如果队尾为null,将队首设为一个空节点


此时AQS的线程队列长这样




进入循环,thread2获取不到锁,执行shouldParkAfterFailedAcquire()


将前驱节点的waitStatus设为Node.SIGNAL(-1);返回false,进入下一个循环,执行parkAndCheckInterrupte(),理解为阻塞当前线程。

此时AQS队列


这是thread3也开始加锁,AQS变化如下



这时,thread1业务执行完成,执行unlock();

 




AQS的队列变化为下,进而调用LockSupport.unpark()方法,理解为唤醒阻塞线程thread2



获取锁成功,重新设队首




通过以上的流程完成线程同步。可以看到Lock加锁释放锁的核心就是AQS。