<h1>【JDK源码分析系列】AbstractQueuedSynchronizer 源码分析 -- 锁的获取与释放</h1>
<p><strong>【1】AbstractQueuedSynchronizer 整体架构</strong></p>
<p style="text-align:center;"><img alt="" src="https://beijingoptbbs.oss-cn-beijing.aliyuncs.com/cs/5606289-9ad0578c2541b590baa460abd03e6e27.PNG"></p>
<p>1. AQS 中队列只有两个 : 同步队列 + 条件队列,底层数据结构两者都是链表;<br> 2. 图中有四种颜色的线代表四种不同的场景,1、2、3 序号代表看的顺序</p>
<p><strong>【2】获取锁</strong></p>
<p> Lock.lock () 方法来获得锁,最终目的是想让线程获得对资源的访问权,Lock 一般是 AQS 的子类,lock 方法根据情况一般会选择调用 AQS 的 acquire 或 tryAcquire 方法,acquire 方法 AQS 已经实现了,tryAcquire 方法是等待子类去实现,acquire 方法制定了获取锁的框架,先尝试使用 tryAcquire 方法获取锁,获取不到时,再入同步队列中等待锁,tryAcquire 方法 AQS 中直接抛出一个异常,表明需要子类去实现,子类可以根据同步器的 state 状态来决定是否能够获得锁。</p>
<p><strong>【2.1】同步状态获取流程示意图</strong></p>
<p>大致步骤总结 :</p>
<p>1. 使用 tryAcquire 方法尝试获得锁,获得锁直接返回,获取不到锁的走 2;<br> 2. 把当前线程组装成节点(Node),追加到同步队列的尾部(addWaiter);<br> 3. 自旋,使同步队列中当前节点的前置节点状态为 signal 后,然后阻塞自己;</p>
<p style="text-align:center;"><img alt="" src="https://beijingoptbbs.oss-cn-beijing.aliyuncs.com/cs/5606289-917cc05613bf1a4b62c358dacebbcc7f.jpg"></p>
<p><strong>【2.2】超时获取同步状态流程示意图</strong></p>
<p style="text-align:center;"><img alt="" src="https://beijingoptbbs.oss-cn-beijing.aliyuncs.com/cs/5606289-fbac5c71c3d497459a8687495d8ce31b.jpg"></p>
<p><strong>【2.3】获取锁的相关方法源码分析</strong></p>
<p><strong>【2.3.1】AbstractQueuedSynchronizer -- public final void acquire(int arg)</strong></p>
<pre class="blockcode"><code>/**
* 独占式获取同步状态
* 若当前线程获取同步状态成功,则由该方法返回;
* 否则,进入同步队列等待
*
* 注意在调用该方法时,子类中需要重写tryAcquire(int arg)方法;
*/
// 排它模式下,尝试获得锁
public final void acquire(int arg) {
/**
* tryAcquire(arg) : 该方法为子类可重写的方法
* if判断中前一个条件不成立,即当前线程没有成功获取同步状态,则执行 acquireQueued 方法
* acquireQueued 方法 : 该方法用于已经排队的线程在独占非中断模式下尝试获取同步状态
*/
// tryAcquire 方法是需要实现类去实现的,
// 实现思路一般都是 cas 给 state 赋值来决定是否能获得锁
if (!tryAcquire(arg) &&
// addWaiter 入参代表是排他模式
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//中断当前线程
selfInterrupt();
}</code></pre>
<p>流程说明(流程见整体架构图中红色场景) :</p>
<p>1. 尝试执行一次 tryAcquire,如果成功直接返回,失败走 2;<br> 2. 线程尝试进入同步队列,首先调用 addWaiter 方法,把当前线程放到同步队列的队尾;<br> 3. 接着调用 acquireQueued 方法,两个作用,1:阻塞当前节点,2:节点被唤醒时,使其能够获得锁;<br> 4. 如果 2、3 失败了,中断线程;</p>
<p><strong>【2.3.2】AbstractQueuedSynchronizer -- final boolean acquireQueued(final Node node, int arg)</strong></p>
<pre class="blockcode"><code>/**
* 该方法用于已经排队的线程尝试获取同步状态;
* 队列中的节点做自旋操作;
*/
// 主要做两件事情:
// 1:通过不断的自旋尝试使自己前一个节点的状态变成 signal,然后阻塞自己
// 2:获得锁的线程执行完成之后,释放锁时,会把阻塞的 node 唤醒, node 唤醒之后再次自旋,尝试获得锁
// 返回 false 表示获得锁成功,返回 true 表示失败
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
// 死循环,不断地尝试在当前节点获取同步状态
// 即队列中的节点进行自旋操作
for (;;) {
// 选上一个节点
//p 为当前节点的前驱节点
final Node p = node.predecessor();
//若 p 为当前队列的首节点,即当前节点为队列首节点的后继节点
//并且当前节点尝试获取同步状态成功
//
// 有两种情况会走到 p == head:
// 1:node 之前没有获得锁,进入 acquireQueued 方法时,才发现他的前置节点就是头节点,于是尝试获得一次锁;
// 2:node |
|