Lock框架为java并发编程提供了除synchronized之外的另外一种抉择。synchronized是隐式实现,底层封装了对锁资源的获取和开释的所有实现细节,程序员不须要关怀也没有方法关怀这些细节,应用起来十分不便也十分平安。

而Lock由java语言实现,公开了锁资源获取和开释的所有细节,在资源锁定过程中提供了更多选项,在获取锁资源后,能够通过Condition对象对锁资源做细粒度的治理。

最要害的是Lock大量应用了CAS,充分利用“持有锁的线程不会长时间占用锁”这一假如,有可能的状况下就尽量先自旋、后锁定资源。所以多线程环境下Lock应该比synchronized有更好的性能。

java线程池框架Executor中大量应用了基于Lock接口的ReentrantLock,把握ReentrantLock是深刻了解各种Executor(ThreadPoolExecutor、ScheduledThreadPoolExecutor等)以及各种阻塞队列的必要前提。

Lock有独占锁、共享锁的区别,独占锁是指某一线程获取锁资源后即独占该锁资源、其余线程只能期待,共享锁是指多个线程能同时取得锁资源。

明天咱们的钻研对象是ReentrantLock,ReentrantLock是独占锁,次要钻研内容:

  1. ReentrantLock的基本概念
  2. 根底数据机构:AQS,CLH队列
  3. 偏心锁、非偏心锁
  4. Condition
  5. 没有Condition参加的lock、unlock
  6. 有Condition参加的lock、unlock

ReentrantLock的基本概念

顾名思义,ReentrantLock是“可重入锁”,意思是同一线程能够屡次取得锁,n次取得须要n次开释能力最终开释掉ReentrantLock。

ReentrantLock的基本原理:

  1. 与synchronized不同,ReentrantLock不存在“锁对象”的概念,或者能够了解为锁对象就是ReentrantLock对象自身
  2. ReentrantLock设置一个状态值,通过对状态值的原子操作实现对锁资源的获取和开释,任何一个线程能获取锁资源的充沛必要条件是ReentrantLock处于闲暇状态,同理,任何一个线程取得锁资源后ReentrantLock即处于占用状态
  3. ReentrantLock的两个最根本的操作:lock和unlock,lock获取锁资源,unlock开释锁资源
  4. ReentrantLock保护一个CLH队列,CLH队列是一个先进先出的双向队列
  5. ReentrantLock处于闲暇状态则lock调用立刻返回,调用线程取得锁资源。否则,申请线程进入CLH队列排队,期待被其余线程唤醒
  6. 取得锁资源的线程在业务执行实现后调用unlock开释锁资源,之后以FIFO的准则唤醒最先进入队列排队的线程
  7. 被唤醒的线程继续执行lock操作,节点从CLH队列出队,返回---意味着申请锁资源的线程在期待后获取锁资源胜利,持续第6步的逻辑

以上是没有Condition对象参加的ReentrantLock的获取、开释锁资源的逻辑,绝对比较简单。

有Condition参加的时候,状况会略微简单一点:

  1. ReentrantLock对象能够通过new Condition()操作持有Condition对象,一个ReentrantLock能够持有多个Condition对象
  2. Condition保护一个Condition队列
  3. Condition的罕用的操作包含await、signal等,执行操作的时候假如以后线程曾经获取到了ReentrantLock锁资源
  4. await操作会开释掉以后线程曾经获取到的ReentrantLock锁资源、挂起以后线程,并且将以后线程退出Condition的队列排队期待被其余线程唤醒。比方DelayedWorkQueue的take办法中,如果以后DelayedWorkQueue队列空的话,则take线程退出到命名为available的Condition中排队等待
  5. 当相干操作可能导致Condition的条件满足的时候,调用Condition的signal办法唤醒在Condition队列中期待的线程。比方上例中DelayedWorkQueue的add办法实现之后,调用available的signal办法,唤醒在available队列中排队等待的线程。
  6. 线程被唤醒之后从Condition队列出队,进ReentrantLock的CLH队列排队期待从新获取锁资源

Condition举例:take办法中队列空的话,挂起期待

Condition举例:offer办法中写入队列后,唤醒期待的线程

对ReentrantLock应该有一个根本的意识了,如果只是想要对ReentrantLock做一个根本理解、可能看懂ReentrantLock的利用、而不是要从源码角度做深入研究的话,集体认为把握下面这些基本原理应该就够了,保障能看懂阻塞队列、线程池中的无关ReentrantLock的源码逻辑了。

然而如果想要彻底搞清楚ReentrantLock到底是怎么实现以上逻辑的,就须要从源码角度持续做深入研究了。

ReentrantLock数据结构:AQS及CLH队列

多个线程同时竞争ReentrantLock锁资源的时候,只能有一个竞争获胜的线程取得锁资源、其余线程就只能排队期待。这个用来排队的队列就是CLH队列,AQS(AbstractQueuedSynchronizer)是实现CLH队列的虚构类。

ReentrantLock有一个十分重要的属性Sync,Sync是AQS的虚构扩大类,Sync有两个实现类:NonfairSync和FairSync,类构造如下:

NonfairSync和FairSync都是AQS的最终实现,AQS虚构类是一个规范模板,定义了Lock锁的根本数据结构(阻塞队列)、并实现了Lock的绝大部分性能。

进入队列排队的线程被封装为Node,Node是AQS定义的外部类,是咱们学习AQS首先要把握的内容。

Node的重要属性:

waitStatus:期待状态,Node就是用来排队的,waitStatus就代表以后节点的期待状态,有以下几种期待状态:

  1. CANCELLED = 1:示意以后期待线程曾经被calcel掉了
  2. SIGNAL = -1:示意该节点是在CLH队列中排队期待出队
  3. CONDITION = -2:示意以后节点是在Condition队列中期待出队
  4. PROPAGATE = -3:共享锁会用到,暂不剖析

prev:上一节点
next:双向队列嘛,当然也要有下一节点
Thread thread:节点的配角,排队线程
nextWaiter:Condition队列专用,用来指向Condition队列的下一节点

AQS的同步队列(CLH)以及Condition队列的节点都是用这个Node,所以Node类做了一部分针对两者的兼容设计,比方nextWaiter是针对Condtion队列的下一节点,next是针对CLH的下一节点。

AQS重要属性

state:锁状态,通过对state的原子操作实现对锁资源的管制:某一线程通过原子操作胜利将state从闲暇批改为占用则意味着以后线程胜利取得了锁资源。无奈取得锁资源的线程则封装为Node节点进入队列排队期待。

head:首节点,头节点
tail:尾结点

通过head节点、tail节点,以及每个节点的prev、next,AQS实现了一个双向队列。

偏心锁和非偏心锁

所谓的偏心锁和非偏心锁就是由Sync属性决定的:当Sync创立为NonfairSync的时候,就是非偏心的ReentrantLock,否则就是偏心的ReentrantLock。

应用无参结构器创立的是非偏心ReentrantLock,有参结构器ReentrantLock(boolean fair)能够通过参数指定创立偏心还是非偏心锁。

偏心锁在线程申请锁资源的时候会查看CLH队列,队列不空的话首先进入队列排队,先提出申请的线程会优先取得锁资源,因而是“偏心”的锁。

非偏心锁在线程申请锁资源的时候不会查看CLH队列,间接尝试取得锁资源,获取失败后才进入队列排队。所以申请线程会失去比队列中的线程更高的优先级,对于队列中排队的线程来说是不偏心的,所以叫非偏心锁。

Condition

Condition提供await和signal(以及他们的变种)办法为ReentrantLock锁资源提供更多抉择:以后线程获取到ReentrantLock锁资源后,能够通过Condition对象的await办法挂起以后线程直到其余线程通过该对象的signal办法唤醒。

一个ReentrantLock能够创立多个Condition对象,每一个Condition对象都是独立的、互不影响。ReentrantLock好比是一条街上的黑社会老大,黑社会老大首先要把这条街拿下,也就是取得ReentrantLock锁资源。之后的每一个Condition好比是这条街道上的饭店A、小卖店B、公共卫生间C,别离对应ConditionObjectA、ConditionObjectB、ConditionObjectC,失去黑社会老大容许后你就能够随便进出饭店吃饭了,然而如果饭店客满了,就必须通过调用ConditionObjectA的await办法进入到ConditionObjectA的队列中排队期待(以后线程封装为AQS中的Node进入队列(假如叫NodeA),以后线程A挂起),此时黑社会老大须要交出对整条街的锁权限(貌似不太正当...),尔后饭店A有人吃完了要离店,就会通过ConditionObjectA的signal办法告诉正在队列中排队等待的NodeA,于是NodeA从ConditionObjectA队列中进去,到ReentrantLock的CLH队列中排队、期待从新获取ReentrantLock锁资源之后再唤醒线程A。这个过程中如果有其他人(其余线程)要进入小卖店B,须要进行操作的就是小卖店对应的ConditionObjectB,和饭店对应的ConditionObjectA没有任何关系。

小结

发现开篇定下的内容太多了,篇幅所限,前面的“没有Condition参加的lock、unlock”以及“有Condition参加的lock、unlock”,根本就是上述逻辑的源码剖析,放在下一篇。

Thanks a lot!

上一篇 周期性工作线程池 - ScheduledThreadPoolExecutor & DelayedWorkQueue