1.5w字,30图带你彻底掌握 AQS!(建议收藏)( 五 )


acquireQueued 剖析如果 tryAcquire(arg) 执行失败 , 代表获取锁失败 , 则执行 acquireQueued 方法 , 将线程加入 FIFO 等待队列
public final void acquire(int arg) {if (!tryAcquire(arg) }所以接下来我们来看看 acquireQueued 的执行逻辑 , 首先会调用 addWaiter(Node.EXCLUSIVE) 将包含有当前线程的 Node 节点入队, Node.EXCLUSIVE 代表此结点为独占模式
再来看下 addWaiter 是如何实现的
private Node addWaiter(Node mode) {Node node = new Node(Thread.currentThread(), mode);Node pred = tail;// 如果尾结点不为空 , 则用 CAS 将获取锁失败的线程入队if (pred != null) {node.prev = pred;if (compareAndSetTail(pred, node)) {pred.next = node;return node;}}// 如果结点为空 , 执行 enq 方法enq(node);return node;}这段逻辑比较清楚 , 首先是获取 FIFO 队列的尾结点 , 如果尾结点存在 , 则采用 CAS 的方式将等待线程入队 , 如果尾结点为空则执行 enq 方法
private Node enq(final Node node) {for (;;) {Node t = tail;if (t == null) {// 尾结点为空 , 说明 FIFO 队列未初始化 , 所以先初始化其头结点if (compareAndSetHead(new Node()))tail = head;} else {// 尾结点不为空 , 则将等待线程入队node.prev = t;if (compareAndSetTail(t, node)) {t.next = node;return t;}}}}首先判断 tail 是否为空 , 如果为空说明 FIFO 队列的 head , tail 还未构建 , 此时先构建头结点 , 构建之后再用 CAS 的方式将此线程结点入队
使用 CAS 创建 head 节点的时候只是简单调用了 new Node() 方法 , 并不像其他节点那样记录 thread , 这是为啥
因为 head 结点为虚结点 , 它只代表当前有线程占用了 state , 至于占用 state 的是哪个线程 , 其实是调用了上文的 setExclusiveOwnerThread(current), 即记录在 exclusiveOwnerThread 属性里 。
执行完 addWaiter 后 , 线程入队成功 , 现在就要看最后一个最关键的方法 acquireQueued 了 , 这个方法有点难以理解 , 先不急 , 我们先用三个线程来模拟一下之前的代码对应的步骤
1、假设 T1 获取锁成功 , 由于此时 FIFO 未初始化 , 所以先创建 head 结点
1.5w字,30图带你彻底掌握 AQS!(建议收藏)文章插图
2、此时 T2 或 T3 再去竞争 state 失败 , 入队 , 如下图:
1.5w字,30图带你彻底掌握 AQS!(建议收藏)文章插图
好了 , 现在问题来了 ,T2 , T3 入队后怎么处理 , 是马上阻塞吗 , 马上阻塞意味着线程从运行态转为阻塞态, 涉及到用户态向内核态的切换 , 而且唤醒后也要从内核态转为用户态 , 开销相对比较大 , 所以 AQS 对这种入队的线程采用的方式是让它们自旋来竞争锁 , 如下图示
1.5w字,30图带你彻底掌握 AQS!(建议收藏)文章插图
不过聪明的你可能发现了一个问题 , 如果当前锁是独占锁 , 如果锁一直被被 T1 占有 ,T2 , T3 一直自旋没太大意义 , 反而会占用 CPU , 影响性能 , 所以更合适的方式是它们自旋一两次竞争不到锁后识趣地阻塞以等待前置节点释放锁后再来唤醒它 。
另外如果锁在自旋过程中被中断了 , 或者自旋超时了 , 应该处于「取消」状态 。
基于每个 Node 可能所处的状态 , AQS 为其定义了一个变量 waitStatus , 根据这个变量值对相应节点进行相关的操作 , 我们一起来看这看这个变量都有哪些值 , 是时候看一个 Node 结点的属性定义了
static final class Node {static final Node SHARED = new Node();//标识等待节点处于共享模式static final Node EXCLUSIVE = null;//标识等待节点处于独占模式static final int CANCELLED = 1; //由于超时或中断 , 节点已被取消static final int SIGNAL = -1;// 节点阻塞(park)必须在其前驱结点为 SIGNAL 的状态下才能进行 , 如果结点为 SIGNAL,则其释放锁或取消后 , 可以通过 unpark 唤醒下一个节点 ,static final int CONDITION = -2;//表示线程在等待条件变量(先获取锁 , 加入到条件等待队列 , 然后释放锁 , 等待条件变量满足条件;只有重新获取锁之后才能返回)static final int PROPAGATE = -3;//表示后续结点会传播唤醒的操作 , 共享模式下起作用//等待状态:对于condition节点 , 初始化为CONDITION;其它情况 , 默认为0 , 通过CAS操作原子更新volatile int waitStatus;