ReentrantLock原理

ReentrantLock

相对于 synchronized 它具备如下特点
  • 可中断
  • 可以设置超时时间
  • 可以设置为公平锁
  • 支持多个条件变量
相同点 与 synchronized 一样,都支持可重入
基本语法
 

可重入

可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁 如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住

可打断

必须使用lock.lockInterruptibly() 获取锁
如果是不可中断模式,那么即使使用了 interrupt 也不会让等待中断

锁超时

使用 tryLock 解决哲学家就餐问题
 

公平锁

ReentrantLock 默认是不公平的
强行插入,有机会在中间输出
改为公平锁后
强行插入,总是在最后输出
公平锁一般没有必要,会降低并发度
 
 
reentrantLock的UML图
reentrantLock的UML图

非公平锁实现原理

加锁解锁流程
先从构造器开始看,默认为非公平锁实现
NonfairSync 继承自 AQS
没有竞争时
没有竞争时 work flow
没有竞争时 work flow
第一个竞争出现时
notion image
Thread-1 执行了
  1. CAS 尝试将 state 由 0 改为 1,结果失败
  1. 进入 tryAcquire 逻辑,这时 state 已经是1,结果仍然失败
  1. 接下来进入 addWaiter 逻辑,构造 Node 队列
      • 图中黄色三角表示该 Node 的 waitStatus 状态,其中 0 为默认正常状态
      • Node 的创建是懒惰的
      • 其中第一个 Node 称为 Dummy(哑元)或哨兵,用来占位,并不关联线程
       
notion image
acquire源码
addWaiter源码
当前线程进入 acquireQueued 逻辑
  1. acquireQueued 会在一个死循环中不断尝试获得锁,失败后进入 park 阻塞
  1. 如果自己是紧邻着 head(排第二位),那么再次 tryAcquire 尝试获取锁,当然这时 state 仍为 1,失败
  1. 进入 shouldParkAfterFailedAcquire 逻辑,将前驱 node,即 head 的 waitStatus 改为 -1,这次返回 false
    1. notion image
  1. shouldParkAfterFailedAcquire 执行完毕回到 acquireQueued ,再次 tryAcquire 尝试获取锁,当然这时state 仍为 1,失败
  1. 当再次进入 shouldParkAfterFailedAcquire 时,这时因为其前驱 node 的 waitStatus 已经是 -1,这次返回true
  1. 进入 parkAndCheckInterrupt, Thread-1 park(灰色表示)
    1. notion image
  1. 再次有多个线程经历上述过程竞争失败,变成这个样子
    1. notion image
 
acquireQueued对应源码
 
解锁流程
  • 设置 exclusiveOwnerThread 为 null
  • state = 0
notion image
 
当前队列不为 null,并且 head 的 waitStatus = -1,进入 unparkSuccessor 流程
找到队列中离 head 最近的一个 Node(没取消的),unpark 恢复其运行,本例中即为 Thread-1
回到 Thread-1 的 acquireQueued 流程
notion image
 
如果加锁成功(没有竞争),会设置
  • exclusiveOwnerThread 为 Thread-1,state = 1
  • head 指向刚刚 Thread-1 所在的 Node,该 Node 清空 Thread
  • 原本的 head 因为从链表断开,而可被垃圾回收
如果这时候有其它线程来竞争(非公平的体现),例如这时有 Thread-4 来了
notion image
如果不巧又被 Thread-4 占了先
  • Thread-4 被设置为 exclusiveOwnerThread,state = 1
  • Thread-1 再次进入 acquireQueued 流程,获取锁失败,重新进入 park 阻塞
 
 
解锁源码

可重入原理

 

可打断原理

  1. 不可打断模式
    1. 可打断模式
       

      公平锁实现原理

      与非公平锁主要区别在于 tryAcquire 方法的实现
       

      条件变量实现原理

      每个条件变量其实就对应着一个等待队列,其实现类是 ConditionObject,它是AQS的一个静态内部类
      ConditionObject的UML图
      ConditionObject的UML图
      await流程
       
      开始 Thread-0 持有锁,调用 await,进入 ConditionObject 的 addConditionWaiter 流程 创建新的 Node 状态为 -2(Node.CONDITION),关联 Thread-0,加入等待队列尾部
      notion image
      (1) addConditionWaiter 源码
      接下来进入 AQS 的 fullyRelease 流程,释放同步器上的锁
      notion image
      unpark AQS 队列中的下一个节点,竞争锁,假设没有其他竞争线程,那么 Thread-1 竞争成功
      notion image
      park 阻塞 Thread-0
      notion image
       
       
      signal流程
      假设 Thread-1 要来唤醒 Thread-0
      进入 ConditionObject 的 doSignal 流程,取得等待队列中第一个 Node,即 Thread-0 所在 Node
      notion image
      执行 transferForSignal 流程,将该 Node 加入 AQS 队列尾部,将 Thread-0 的 waitStatus 改为 0,Thread-3 的waitStatus 改为 -1
      notion image
      Thread-1 释放锁,进入 unlock 流程