1.第一个线程进来拿到锁

image.png

2.第二个线程来抢锁

image.png

3.第二个线程加锁失败的场景

第二个线程执行了
1. CAS 尝试将 state 由 0 改为 1,结果失败
2. 进入 tryAcquire 逻辑,这时 state 已经是1,结果仍然失败
3. 接下来进入 addWaiter 逻辑,构造 Node 队列

  • 图中黄色三角表示该 Node 的 waitStatus 状态,其中 0 为默认正常状态
  • Node 的创建是懒惰的
  • 其中第一个 Node 称为Dummy(哑元 或 哨兵)节点,用来占位,并不关联线程

image.png

3.1 当前线程进入 acquireQueued 逻辑

  1. acquireQueued 会在一个死循环中不断尝试获得锁,失败后进入 park 阻塞
  2. 如果自己是紧邻着 head(排第二位),那么再次 tryAcquire 尝试获取锁,当然这时 state 仍为 1,失败
  3. 进入 shouldParkAfterFailedAcquire 逻辑,将前驱 node,即 head 的 waitStatus 改为 -1,这次返回 false
  4. shouldParkAfterFailedAcquire 执行完毕回到 acquireQueued ,再次 tryAcquire 尝试获取锁,当然这时
    state 仍为 1,失败
  5. 当再次进入 shouldParkAfterFailedAcquire 时,这时因为其前驱 node 的 waitStatus 已经是 -1,这次返回
    true
  6. 进入 parkAndCheckInterrupt, Thread-1 park(灰色表示)

image.png

3.2 后续线程加锁失败

image.png


4. tryRelease

此时Thread-0 释放锁,进入 tryRelease 解锁流程,如果成功

  • 设置 exclusiveOwnerThread 为 null
  • state = 0

image.png

  • 当前队列不为 null,并且 head 的 waitStatus = -1,进入 unparkSuccessor 流程,唤醒后继节点
  • 找到队列中离 head 最近的一个 Node(没取消的),unpark 恢复其运行,本例中即为 Thread-1
  • 回到 Thread-1 的 acquireQueued 流程

5. 线程被唤醒

image.png

进入被唤醒的线程的 acquireQueued 流程

如果加锁成功(没有竞争),会设置 exclusiveOwnerThread 为 Thread-1,state = 1
head 指向刚刚 Thread-1 所在的 Node,该 Node 清空 Thread,成为新的哨兵节点
原本的 head 因为从链表断开,而可被垃圾回收
如果这时候有其它线程来竞争(非公平的体现),例如这时有 Thread-4 来了
image.png
如果不巧又被 Thread-4 占了先

  • Thread-4 被设置为 exclusiveOwnerThread,state = 1
  • Thread-1 再次进入 acquireQueued 流程,获取锁失败,重新进入 park 阻塞 :::tips 是否需要 unpark 是由当前节点的前驱节点的 waitStatus == Node.SIGNAL 来决定,而不是本节点的waitStatus 决定 :::