ReentrantLock
ReentrantLock重入锁可以显示的加锁释放锁,且可以配合Condition指定阻塞和唤醒线程,相比synchronized更加灵活。并且已api接口形式提供给开发,我们可以直接阅读源码,看下底层是如何进行锁的实现。
一. ReentrantLock
1.1 成员变量和构造方法
Sync是ReentrantLock的静态内部列,其实现了AbstractQueuedSynchronizer(抽象队列同步器,简称AQS,很重要)
NonfairSync和FairSync都继承了Sync,分别为非公平锁和公平锁的实现。
无参构造方法为非公平锁;有参构造可以自定位为公平锁还是非公平锁。
1.2 方法(重点介绍lock,unlock)
1.2.1 lock的实现
1.2.1.1 非公平锁实现
(1)假设现在有场景:thread1来加锁,执行业务流程,释放锁的
lock()加锁
unlock()释放锁
AQS里的release方法,该方法是一个模板方法,子类都会调用到该方法,但是里面的抽象方法tryRelease子类有各自的实现,下面看Syn里的实现
判断AQS里的state值(private volatile int state),该变量用volatile修饰,主要是为了多个线程之间的通信,并且可以防止指令重排序。
加锁的时候通过cas操作已经将state设为1,这里减去1后要判断下是否为0(因为ReentrantLock是可重入锁,后续场景会讲到),如果为0,则将当前锁用者设为null,更新state,释放锁成功。
(2)假设现在有场景:thread1,thread2,thread3依次来加锁,执行业务流程,释放锁的。thread1获取到锁,并且一直未释放锁。
那么thread2CAS操作失败,会执行到acquire()方法
AQS里的acquire方法同release方法一样,该方法是个模板方法,给子类调用,里面的抽象方法tryAcquire子类有各自的实现
先获取state,如果为0就尝试获取锁;如果非0,就判断当前拥有锁者是否为自己,如果为自己就将state加1,这也就是重入锁的实现。
在我们的场景里,这里会返回false,那么就继续执行AQS里的acquireQueued方法,我们先看addWaiter方法
新建一个包含当前线程的Node,加入AQS的线程队列中
如果队尾为null,将队首设为一个空节点
此时AQS的线程队列长这样
进入循环,thread2获取不到锁,执行shouldParkAfterFailedAcquire()
将前驱节点的waitStatus设为Node.SIGNAL(-1);返回false,进入下一个循环,执行parkAndCheckInterrupte(),理解为阻塞当前线程。
此时AQS队列
这是thread3也开始加锁,AQS变化如下
这时,thread1业务执行完成,执行unlock();
AQS的队列变化为下,进而调用LockSupport.unpark()方法,理解为唤醒阻塞线程thread2
获取锁成功,重新设队首
通过以上的流程完成线程同步。可以看到Lock加锁释放锁的核心就是AQS。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
2020-08-03 PostgreSQL 常用函数