Java并发编程Lock Condition和ReentrantLock基本原理

 更新时间:2023年09月15日 11:41:31   作者:福  
这篇文章主要介绍了Java并发编程Lock Condition和ReentrantLock基本原理,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

Lock框架

Lock框架为java并发编程提供了除synchronized之外的另外一种选择。synchronized是隐式实现,底层封装了对锁资源的获取和释放的所有实现细节,程序员不需要关心也没有办法关心这些细节,使用起来非常方便也非常安全。

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

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

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

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

今天我们的研究对象是ReentrantLock,ReentrantLock是独占锁,主要研究内容:

  • ReentrantLock的基本概念
  • 基础数据机构:AQS,CLH队列
  • 公平锁、非公平锁
  • Condition
  • 没有Condition参与的lock、unlock
  • 有Condition参与的lock、unlock

ReentrantLock的基本概念

顾名思义,ReentrantLock是“可重入锁”,意思是同一线程可以多次获得锁,n次获得需要n次释放才能最终释放掉ReentrantLock。

ReentrantLock的基本原理:

  • 与synchronized不同,ReentrantLock不存在“锁对象”的概念,或者可以理解为锁对象就是ReentrantLock对象本身
  • ReentrantLock设置一个状态值,通过对状态值的原子操作实现对锁资源的获取和释放,任何一个线程能获取锁资源的充分必要条件是ReentrantLock处于空闲状态,同理,任何一个线程获得锁资源后ReentrantLock即处于占用状态
  • ReentrantLock的两个最基本的操作:lock和unlock,lock获取锁资源,unlock释放锁资源
  • ReentrantLock维护一个CLH队列,CLH队列是一个先进先出的双向队列
  • ReentrantLock处于空闲状态则lock调用立即返回,调用线程获得锁资源。否则,请求线程进入CLH队列排队,等待被其他线程唤醒
  • 获得锁资源的线程在业务执行完成后调用unlock释放锁资源,之后以FIFO的原则唤醒最先进入队列排队的线程
  • 被唤醒的线程继续执行lock操作,节点从CLH队列出队,返回---意味着请求锁资源的线程在等待后获取锁资源成功,继续第6步的逻辑

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

有Condition参与的时候,情况会稍微复杂一点:

  • ReentrantLock对象可以通过new Condition()操作持有Condition对象,一个ReentrantLock可以持有多个Condition对象
  • Condition维护一个Condition队列
  • Condition的常用的操作包括await、signal等,执行操作的时候假设当前线程已经获取到了ReentrantLock锁资源
  • await操作会释放掉当前线程已经获取到的ReentrantLock锁资源、挂起当前线程,并且将当前线程加入Condition的队列排队等待被其他线程唤醒。比如DelayedWorkQueue的take方法中,如果当前DelayedWorkQueue队列空的话,则take线程加入到命名为available的Condition中排队等候
  • 当相关操作可能导致Condition的条件满足的时候,调用Condition的signal方法唤醒在Condition队列中等待的线程。比如上例中DelayedWorkQueue的add方法完成之后,调用available的signal方法,唤醒在available队列中排队等候的线程。
  • 线程被唤醒之后从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就代表当前节点的等待状态,有以下几种等待状态:

  • CANCELLED = 1:表示当前等待线程已经被calcel掉了
  • SIGNAL = -1:表示该节点是在CLH队列中排队等待出队
  • CONDITION = -2:表示当前节点是在Condition队列中等待出队
  • 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”,基本就是上述逻辑的源码分析,放在下一篇。

以上就是Java并发编程Lock Condition和ReentrantLock基本原理的详细内容,更多关于Java并发编程的资料请关注脚本之家其它相关文章!

相关文章

  • Java Synchronized字节码层分析体验

    Java Synchronized字节码层分析体验

    这篇文章主要介绍了Java Synchronized字节码层分析,synchronized关键字解决了多个线程之间的资源同步性,synchronized关键字保证了它修饰的方法或者代码块任意时刻只有一个线程在访问
    2023-04-04
  • JVM 命令行工具的使用

    JVM 命令行工具的使用

    造成Java应用出现性能问题的因素非常多,想要定位这些问题,一款优秀的性能诊断工具必不可少,本文主要介绍了JVM 命令行工具的使用,具有一定的参考价值,感兴趣的可以了解一下
    2024-04-04
  • Java链表元素查找实现原理实例解析

    Java链表元素查找实现原理实例解析

    这篇文章主要介绍了Java链表元素查找实现原理实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-07-07
  • Java concurrency线程池之线程池原理(二)_动力节点Java学院整理

    Java concurrency线程池之线程池原理(二)_动力节点Java学院整理

    这篇文章主要为大家详细介绍了Java concurrency线程池之线程池原理第二篇,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-06-06
  • SpringBoot处理跨域请求的四种方法

    SpringBoot处理跨域请求的四种方法

    在现代Web应用中,由于安全性和隐私的考虑,浏览器限制了从一个域向另一个域发起的跨域HTTP请求,解决这个问题的一种常见方式是实现跨域资源共享(CORS),SpringBoot提供了多种方式来处理跨域请求,本文将介绍其中的几种方法,感兴趣的朋友可以参考下
    2023-12-12
  • Java项目中添加外部jar包的两种方式(收藏版)

    Java项目中添加外部jar包的两种方式(收藏版)

    这篇文章主要介绍了java项目中添加外部jar包的两种方式,第二种方式是将外部jar包引入到本地maven仓库中,本文给大家讲解的非常详细,需要的朋友可以参考下
    2023-03-03
  • 说说在Spring中如何引用外部属性文件的方法

    说说在Spring中如何引用外部属性文件的方法

    这篇文章主要介绍了说说在Spring中如何引用外部属性文件的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-05-05
  • Java实现可配置换肤的方法示例

    Java实现可配置换肤的方法示例

    本文主要介绍了Java实现可配置换肤的方法示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-06-06
  • 浅析Java SPI 与 dubbo SPI

    浅析Java SPI 与 dubbo SPI

    在Java中SPI是被用来设计给服务提供商做插件使用的。本文重点给大家介绍Java SPI 与 dubbo SPI的相关知识及区别介绍,感兴趣的朋友跟随小编一起学习下吧
    2021-05-05
  • idea中将单个java类导出为jar包文件的方法

    idea中将单个java类导出为jar包文件的方法

    这篇文章主要给大家介绍了关于idea中将单个java类导出为jar包文件的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-09-09

最新评论