尽量搞懂ReentrantLock原理

ReentrantLock原理

在Java5.0之前,在协调对共享对象的访问时可以使用的机制只有synchronized和volatile,Java5.0增加了一种新机制:ReentrantLock,与之前提到的机制相反,ReentrantLock并不是一种替代内置加锁的方法,而是当内置加锁机制不适用的时候,作为一种可选择的高级功能。

ReentrantLock实现了Lock接口,Lock提供了一种无条件的、可轮询的、定时的以及可中断的锁获取操作,所有加锁和解锁的方法都是显示的。

ReentrantLock相对synchronized具有如下特点:

  • 可中断
  • 可以设置超时时间
  • 可以设置为公平锁
  • 支持多了条件变量
  • ReentrantLock和synchronized都支持锁重入

非公平锁实现原理

加锁解锁流程

先从构造器开始看,默认为非公平实现

public ReentrantLock(){
    sync=new NonfairSync();
}

NonfairSync继承自AQS。

没有竞争时:即只有一个线程的时候,Thread-0在获得锁的时候,首先会使用cas方式将state状态将0改为1,修改成功之后,exclusiveOwnerThread为Thread-0,表示加锁成功。

第一个竞争出现时:表示再来一个线程也要加锁的时候,来了个Thread-1,这个时候Thread-1也去尝试用cas的方式将state从0改为1,但是失败了

上图中的Thread-1执行了:

  • CAS尝试将state由0改为1,结果失败
  • 进入tryAcquire原理,这个时候state已经是1,结果仍然失败
  • 接下来进入addWaiter逻辑,构造Node队列:
  1. 下图中黄色三角形表示该Node的waitStatus状态,其中0为默认正常状态
  2. Node的创建是懒惰的
  3. 其中第一个Node称为Dummy(哑源)或者哨兵,用来占位,并不关联线程

当前线程进入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,Thead-1 park(灰色表示)

再次有多个线程经历上述过程竞争失败,变成这个样子

Thead-0释放锁之后,进入tryRelease流程,如果成功:

  • 设置exclusiveOwnerThread为null
  • 将state置为0

当队列不为null,并且head的waitStatus=-1,进入unparkSuccessor流程

找到队列中离head最近的一个node(没有取消的),unpark恢复其运行,本图中为Thread-1

回到Thead-1的acquireQueued流程

如果加锁成功(没有竞争),会设置

  • exclusiveOwnerThread为Thead-1,state=1
  • head指向刚刚Thead-1所在的Node,该Node清空Thead
  • 原本的head因为从链表断开,而可被垃圾回收

如果这个时候有其他线程来竞争(非公平的体现),例如这个时候有Thead-4来了

如果不巧又被Thead-4占了先机:

  • Thead-4被设置为exclusiveOwnerThread,state=1
  • Thead-1再次进入acquireQueued流程,获取锁失败,重新进入park阻塞

锁重入原理

锁重入加锁:

ReentrantLock支持锁重入,首先判断当前持有的锁是不是当前线程,如果是,那么当前线程进行锁重入的时候,直接将state++;

锁冲入释放锁:

释放锁的时候,就是state--;

可打断原理

不可打断模式

在此模式下,即使它被打断,仍然会驻留在AQS队列中,等待获得锁后方能继续运行(是继续运行,只是打断标志被设置为true)

可打断模式

使用抛出异常的方式

公平锁原理

非公平锁实现:

公平锁实现:

条件变量

每个条件变量其实就对应着一个等待队列,其实现类就是ConditionObject,类似于synchronized中的waitSet作用,只不过synchronized中所有不满足条件的都进入waitSet中,而ReentrantLock中不满足条件的按照类别进入不同的等待队列中,这样到时候唤醒线程就可以有目标的唤醒。

await流程

开始Thead-0持有锁,如果调用await(),那就会进入ConditionObject的addConditionWaiter流程

创建新的Node状态为-2(Node.CONDITION),关联Thead-0,加入等到队列

接下来进入AQS的fullyRelease流程,释放同步器上的锁。所以调用wait()和await()都会释放锁。

Thead-0释放了锁,那么就会唤醒unpark AQS队列中的下一个节点竞争锁,假设没有其他竞争线程,那Thead-1竞争成功

park阻塞Thread-0

signal流程

假设Thead-1要来唤醒Thead-0,只有Owner线程才有资格唤醒条件变量中等待的线程

进入ConditionObject的doSignal流程,取得等待队列中第一个Node,即Thead-0所在的Node

执行transferForSignal流程,将该Node加入AQS队列尾部,将Thead-0的waitStatue改为0,Thead-3的waitStatus改为-1

Thead-1释放锁,进入unpark流程。

猜你喜欢

转载自blog.csdn.net/kidchildcsdn/article/details/113877813
今日推荐