AQS
Java并发之AQS源码分析(一) - 云+社区 - 腾讯云 (opens new window)
🤔方向点:
* 双端队列
* waitStatus
* 公平锁与非公平锁
* 共享与非共享
* 重入锁
# 获取锁流程
1. 调用 acquire() 方法,(arg参数可以是任意的,在ReentrantLock中就是上锁数)
2. 调用tryAcquire() 方法,如果返回 false,也就是获取锁失败,将Node(exclusive)信息添加到队列末端
3. 执行 acquireQueued 的逻辑,该逻辑主要是判断当前节点的前置节点是否是头节点,来尝试获取锁,如果获取锁成功,则当前节点就会成为新的头节点,调用parkAndCheckInterrupt方法挂起线程
# state 变量
State 是每次加锁的时候加1,大于0说明存在锁,释放锁的时候减1。
# 公平锁与非公平锁
在AQS中并没有实现公平与非公平锁,这个功能是在ReentrantLock实现。
# 共享与非共享
提供了独占锁和共享锁的接口,但tryAcquire、tryAcquireShared接口都没有提供实现。
# waitState 变量
1. CANCELLED(1):取消状态,如果当前线程的前置节点状态为 CANCELLED,则表明前置节点已经等待超时或者已经被中断了,这时需要将其从等待队列中删除。
2. SIGNAL(-1):等待触发状态,如果当前线程的前置节点状态为 SIGNAL,则表明当前线程需要阻塞。
3. CONDITION(-2):等待条件状态,表示当前节点在等待 condition,即在 condition 队列中。
4. PROPAGATE(-3):状态需要向后传播,表示 releaseShared 需要被传播给后续节点,仅在共享锁模式下使用。
Thread 字段用于存储当前线程对象
# enq()方法
如果队尾节点为空,则初始化队列,将头节点设置为空节点,头节点即表示当前正在运行的节点;如果队尾节点不为空,则继续采取 CAS 操作,将当前节点加入队尾,不成功则继续自旋,直到成功为止;
# acquireQueued(final Node node, int arg)
判断当前节点的 pred 节点是否为 head 节点,如果是,则尝试获取锁;2.获取锁失败后,进入挂起逻辑。 提醒一点:我们上面也说过,head 节点代表当前持有锁的线程,那么如果当前节点的 pred 节点是 head 节点,很可能此时 head 节点已经释放锁了,所以此时需要再次尝试获取锁。
# shouldParkAfterFailedAcquire(Node pred, Node node)
判断 pred 节点状态,如果为 SIGNAL 状态,则直接返回 true 执行挂起;2.删除状态为 CANCELLED 的节点;3.若 pred 节点状态为 0 或者 PROPAGATE,则将其设置为为 SIGNAL,再从 acquireQueued 方法自旋操作从新循环一次判断。
通俗来说就是:根据 pred 节点状态来判断当前节点是否可以挂起,如果该方法返回 false,那么挂起条件还没准备好,就会重新进入 acquireQueued(final Node node, int arg) 的自旋体,重新进行判断。如果返回 true,那就说明当前线程可以进行挂起操作了,那么就会继续执行挂起。
释放锁主要是将头节点的后继节点唤醒,如果后继节点不符合唤醒条件,则从队尾一直往前找,直到找到符合条件的节点为止。
AQS 中阻塞挂起线程是通过 LockSupport.park()
[[LockSupport]] 实现。