【并发编程】 --- Reentrantlock源码解析2:公平锁加锁过程超详细解析

el/2023/10/1 3:25:29

文章目录

  • 1 前情回顾 --- 同步方法交替执行时Reentrantlock公平锁的逻辑
  • 2 线程t1抢到锁并且没释放的情况
    • 2.1 线程t2、t3、t4......入队 --- addWaiter(Node.EXCLUSIVE), arg)方法
    • 2.2 线程 t2、t3、t4...入队后 ---》自旋 + park
      • 2.2.1 前置知识 --- Node数据结构介绍
      • 2.2.2 线程 t2、t3、t4...入队后 ---》自旋 + park的具体流程
  • 3 Reentrantlock公平锁加锁过程总结

源码地址:https://github.com/nieandsun/concurrent-study.git


1 前情回顾 — 同步方法交替执行时Reentrantlock公平锁的逻辑

前面一篇文章《【并发编程】 — Reentrantlock源码解析1:同步方法交替执行的处理逻辑》讲过无论是synchronized还是Lock锁,让某块代码变为同步的本质就是: 当一个线程执行该方法后,其他线程无法进入该方法,对应于Reentrantlock来说就是让其他线程的lock()方法无法正常返回。

那篇文章里讲过在方法交替执行时,Reentrantlock公平锁的主要逻辑如下,这里就不再过多叙述。
在这里插入图片描述


2 线程t1抢到锁并且没释放的情况


2.1 线程t2、t3、t4…入队 — addWaiter(Node.EXCLUSIVE), arg)方法

在这里插入图片描述
线程2的入队代码就这么一点,但是过程挺有意思的,这里总结如下:
在这里插入图片描述
当然这只是一种情况,即线程t2 成功排在了队列第2的位置。

其实应该还有一种情况: 即线程t2完成了上图中粉色字体的第(2)步,这时线程t3刚好进入addWaiter()方法,发现tail已经不为null了,这时候有可能t3会先排在队列第2的位置 ,这里就不画图了。。。

当然还有可能刚开始时一下有多个线程进入enq方法的情况,这里就不展开了。。。

—> 此时应该可以引申出一个思考: Reentrantlock所谓的公平锁,到底公平在哪???
—> 从这里我们可以看出来,这里所谓的公平并不是你先尝试获取锁,你就一定会最先获取到锁,而是你最先进入到了Node队列,你最先获取到锁!!! — 这一点我感觉也是非常非常重要。

非常注意三点(即我在图中用红色字体写的三句话):

  • (1)只有被阻塞的线程才会进入Node链表进行排队 —> 也就是说获得锁的线程不会参与排队
  • (2)head节点的Thread永远为null —》 也就是Node链表中的第2个节点是最先进行排队的线程
  • (3)当一个线程发现前面有Node时,它会直接排在队尾 —》 也就是说当线程3、4。。刚进入addWaiter方法要排队时,直接就一个个往后排,不用再进enq方法了

本文后面的内容都假设t2的Node排在了队列第2的位置。


2.2 线程 t2、t3、t4…入队后 —》自旋 + park

其实这两点应该可以很容易的想到:

  • (1)前面入队只是为了让线程按照先后顺序被唤醒,但是要想阻塞线程,肯定还是得让其进行park
  • (2)但是因为线程的park和unpark肯定要涉及到用户态和内核态的来回切换,所以在真正的进行park之前先去自旋一下看一看前面的线程是不是已经释放锁了—> 这样就有可能不用真正的park了 。

该逻辑在前面讲JDK1.6对synchronized关键字的优化《【并发编程】 — synchronized锁的升级过程 + JDK1.6对synchronized关键字的其他优化简介 》时也讲到过 — 但由于没法调试,我们其实很难真正去了解它到底是怎么实现的。

接下来我们从源码角度看看Doug Lea大神的具体实现方式。


2.2.1 前置知识 — Node数据结构介绍

在介绍Doug Lea大神的具体实现方式之前先来介绍一下Node的具体数据结构,因为只有知道了里面的一个变量waitStatus,才能更好的明白其实现原理。
Node的结构如下:
**加粗样式**
概括起来可以分为三个部分

  • (1)线程的2种等待模式:

    • SHARED:表示线程以共享的模式等待锁(如ReadLock)
    • EXCLUSIVE:表示线程以互斥的模式等待锁(如ReetrantLock),互斥就是一把锁只能由一个线程持有,不能同时存在多个线程使用同一个锁
  • (2)线程在队列中的状态枚举:

    • CANCELLED:值为1,表示线程的获锁请求已经“取消”
    • SIGNAL:值为-1,表示该线程一切都准备好了,就等待锁空闲出来给我
    • CONDITION:值为-2,表示线程等待某一个条件(Condition)被满足 —》 相当于wait、notify/notifyAll的用法
    • PROPAGATE:值为-3,当线程处在“SHARED”模式时,该字段才会被使用上
    • 初始化Node对象时,默认为0
  • (3)成员变量:

    • waitStatus:该int变量表示线程在队列中的状态,其值就是上述提到的CANCELLED、SIGNAL、CONDITION、PROPAGATE
    • prev:该变量类型为Node对象,表示该节点的前一个Node节点(前驱)
    • next:该变量类型为Node对象,表示该节点的后一个Node节点(后继)
    • thread:该变量类型为Thread对象,表示该节点的代表的线程
    • nextWaiter:该变量类型为Node对象,表示等待condition条件的Node节点

注意: 只有未获得锁,或者说被阻塞的方法才会进入Node链表中 , 已经获得锁的线程肯定不会进入Node链表。


知道了Node的数据结构之后,还必须知道Reentrantlock的公平锁和和非公平锁都是独占锁 —》因此在不涉及到线程交互的情况下,公平锁的waitStatus只可能有三个值,即1 --- CANCELLED、-1 --- SIGNAL 和 0 ---初始化Node时的默认值。 —> 这一点务必要记住!


2.2.2 线程 t2、t3、t4…入队后 —》自旋 + park的具体流程

具体流程总结如下:
在这里插入图片描述


3 Reentrantlock公平锁加锁过程总结

自认为Reentrantlock公平锁的加锁过程被我这么一总结,还挺好理解的(☆_☆) ,为了更深刻一些,这里再做一个总结:
在这里插入图片描述
如果让我再用几句更简单的话来总结的话,我会这样进行总结:

  • (1)排队时,队首肯定是一个Thread为null的Node,其他线程的Node每次入队时都排在队尾(并维护双向链表关系)

  • (2)自旋 + park的过程 —》 如上图,哈哈哈!!!


end!


http://www.ngui.cc/el/1113659.html

相关文章

【并发编程】 --- Reentrantlock源码解析3:公平锁释放锁过程超详细解析

文章目录1 公平锁释放锁的过程1.1 从源码中看公平锁释放锁的过程1.2 以源码为基础总结出的公平锁释放锁的流程2 需要思考的问题2.1 问题1 --- 释放锁的过程中有可能导致尾节点的ws为-1么 ---> 不可能2.2 问题2 --- 公平锁加锁和解锁的过程还有值得探究的地方么? …

【并发编程】 --- Reentrantlock源码解析4:公平锁加锁过程中 [判断当前线程是否要排队的具体细节] 超详细解析

文章目录1 简单回顾2 源码 整体逻辑流程梳理3 高并发环境下当前线程使用公平锁时判断自己是否要排队的具体实现细节3.1 h ! t3.1.1 h ! t 不成立时(即h等于t时) ---> 不用排队的原因3.1.1.1 情况1 ---> h和t都等于null ---> 不用排队3.1.1.2 情…

【并发编程】 --- Reentrantlock源码解析5:再探不可中断性 + 线程unpark后诡异的Thread.interrupted()判断

文章目录1 想要读懂这篇文章必须要拥有的前置知识2 想写这篇文章的原因3 困扰我很久的Reentrantlock源代码1 --- 貌似无用的变量failed4 困扰我很久的Reentrantlock源代码2 --- unpark后为啥要来个Thread.interrupted();5 小记源码地址:https://github.com/nieandsu…

【并发编程JVM】--- 强软弱虚四种引用 + ThreadLocal内存泄漏原因分析

本篇文章整理自马士兵老师的公开课(哔哩哔哩) 源码地址:https://github.com/nieandsun/concurrent-study.git 文章目录【1】强软弱虚四种引用【1.1】强引用【1.2】软引用【1.3】弱引用【1.4】虚引用【2】ThreadLocal使用方式 底层原理分析【…

【并发编程】 --- Lock/Condition完成生产者消费者模式+ABCABC顺序打印问题

源码地址:https://github.com/nieandsun/concurrent-study.git 文章目录1 生产者消费者问题2 ABCABC。。。三个线程顺序打印问题2.1 基本不费脑子的实现方式 --- 且比较容易感受到定点通知的含义2.2 比较灵活的方式1 生产者消费者问题 使用一个Condition极其类似于w…

【并发编程】 --- 从五个维度对比synchronized关键字和Lock

文章目录维度1 --- 从原始构成上来说维度2 --- 从使用方法上来说维度3 --- 从等待是否可中断上来说维度4 --- 从加锁是否公平角度来说维度5 --- 从线程间的通信来说维度1 — 从原始构成上来说 synchronized是关键字,属于JVM层面Lock是具体类(java.util.concurrent.…

【并发编程】--- 阻塞队列(BlockingQueue)简介

源码地址:https://github.com/nieandsun/concurrent-study.git 文章目录1 阻塞队列的含义2 为什么用? 有什么好处?3 常用的BlockingQueue3.1 ArrayBlockingQueue简介3.2 LinkedBlockingQueue简介3.3 PriorityBlockingQueue简介3.4 DelayQueue…

【并发编程】--- 线程池七大参数+四种拒绝策略 + 如何合理配置线程数等简介

源码地址:https://github.com/nieandsun/concurrent-study.git 文章目录1 线程池七大参数2 RejectedExecutionHandler--- 四种拒绝策略(官方提供)3 threadFactory --- 线程工厂相关的注意事项4 如何自己new一个线程池 --- 简单结合了一下我们…

【消息中间件】--- RocketMQ核心概念介绍 + 生产者简介 + 消息存储简介

本文对应源码地址:https://github.com/nieandsun/rocketmq-study rocketmq官网:https://rocketmq.apache.org/docs/quick-start/ rocketmq github托管地址(这里直接给出的是中文docs地址):https://github.com/apache/r…

【消息中间件】--- RocketMQ消费者简介(集群、广播消费,推模式,拉模式)

本文对应源码地址:https://github.com/nieandsun/rocketmq-study rocketmq官网:https://rocketmq.apache.org/docs/quick-start/ rocketmq github托管地址(这里直接给出的是中文docs地址):https://github.com/apache/r…