JAVA里的锁有两大类,一类是synchronized
锁,一类是concurrent
包里的锁(JUC锁
)。其中synchronized
锁是JAVA语言层面提供的能力,本文主要讨论JUC
里的ReentrantLock
锁。
JDK层
AbstractQueuedSynchronizer(AQS)
ReentrantLock
的lock()
、unlock()
等API其实依赖于内部的Synchronizer
(注意不是synchronized
)来实现。Synchronizer
又分为FairSync
和NonfairSync
,顾名思义即公平和非公平。
当调用ReentrantLock
的lock()
方法时,其实就只是简单地转交给Synchronizer
的lock()
方法。
ReentrantLock
的主要方法和结构如下:
1 | public class ReentrantLock implements Lock, java.io.Serializable { |
我们看到Sync
继承自AbstractQueueSynchronizer
(AQS
),AQS
是JUC
包的基石,AQS
本身并不实现任何同步接口(比如lock
、unlock
、countDown
等等),但是它定义了一个并发资源控制逻辑的框架(运用了template method
设计模式),它定义了acquire()
和release()
方法用于独占地(exclusive
)获取和释放资源,以及acquireShared()
和releaseShared()
方法用于共享地获取和释放资源。比如acquire()
/release()
用于实现ReentrantLock
,而acquireShared
/releaseShared
用于实现CountDownLacth
、Semaphore
。
AQS.acquire()
定义如下:
1 | public final void acquire(int arg) { |
整体逻辑是,先进行一次tryAcquire()
,如果成功了,调用者继续执行自己后面的代码;如果失败,则执行addWaiter()
和acquireQueued()()
。其中tryAcquire()
需要子类根据自己的同步需求进行实现,而acquireQueued()
和addWaiter()
已经由AQS实现。addWaiter()
的作用是把当前线程加入到AQS
内部同步队列的尾部,而acquireQueued()
的作用是当tryAcquire()
失败的时候阻塞当前线程。
addWaiter()
的代码如下:
1 | private Node addWaiter(Node mode) { |
enq(node)
的代码如下:
1 | private Node enq(final Node node) { |
- 同步队列的结构如下:
acquireQueued()
的代码如下:
1 | final boolean acquireQueued(final Node node, int arg) { |
判断自己是不是同步队列中的第一个排队的节点,如果是则尝试进行加锁,如果成功,则把自己变成head node
,过程如下所示:
如果自己不是第一个排队的节点或者tryAcquire()
失败,则调用``shouldParkAfterFailedAcquire(),其主要逻辑是使用
CAS将节点状态由
INITIAL设置成
SIGNAL,表示当前线程阻塞等待
SIGNAL唤醒。如果设置失败,会在
acquireQueued()方法中的死循环中继续重试,直至设置成功。然后调用
parkAndCheckInterrupt() 方法,把当前线程阻塞挂起,等待唤醒。
parkAndCheckInterrupt()`的实现需要借助下层的能力,这是本文的重点,在下文中逐层阐述。