首页 > 编程语言 >ReentrantLock源码分析

ReentrantLock源码分析

时间:2023-05-08 09:03:42浏览次数:29  
标签:分析 Node 队列 ReentrantLock state 源码 线程 节点

一、ReentrantLock介绍

  ReentrantLock是JDK1.5引入的,实现Lock接口的互斥锁。保证多线程的环境下,共享资源的原子性。与Synchronized的非公平锁不同,ReentrantLock的实现公平锁、非公平锁。ReentrantLock是重入锁,重入是指,同一个线程可以重复执行加锁的代码段。

二、ReentrantLock使用

  ReentrantLock功能与Synchronized一样,使用起来比Synchronized更加灵活。与Synchronized自动加锁、释放锁不同,ReentrantLock需要手动加锁、释放锁。使用示例如下:

 1 import java.util.concurrent.locks.ReentrantLock;
 2 
 3 public class TestReentrantLock {
 4     public static void main(String[] args) {
 5         ReentrantLock lock = new ReentrantLock();
 6         lock.lock();
 7         try{
 8             // todo
 9         }finally {
10             lock.unlock();
11         }
12     }
13 }

三、ReentrantLock原理

  Synchronized基于对象实现;ReentrantLock基于 AQS + CAS 实现。

3.1、lock()流程图

 

  ReentrantLock基于抽象队列同步器AQS + CAS 实现的加锁、释放锁。ReentrantLock实现了公平锁、非公平锁,公平锁与非公平锁唯一的区别在于,非公平锁不会判断等待队列中是否节点等待获取锁,而是直接尝试获取锁,获取不到,再将当前线程节点添加进等待队列的尾节点,判断当前线程节点是否挂起。

3.2、unlock()流程图

  ReentrantLock释放锁的流程较为简单,优先判断持有锁资源的线程是否为当前线程,若不为当前线程抛出异常;若为当前线程,AQS的state的属性值减1,再判断减1后的值是否为0,若为0表示当前线程彻底释放锁资源,唤醒等待队列中的挂起线程节点,开始抢占锁资源。

四、ReentrantLock源码分析

1、构造函数

 1 private final Sync sync;
 2 
 3 // 默认使用非公平锁
 4 public ReentrantLock() {
 5     sync = new NonfairSync();
 6 }
 7 
 8 // fair=true,公平锁;否则,非公平锁
 9 public ReentrantLock(boolean fair) {
10     sync = fair ? new FairSync() : new NonfairSync();
11 }

  Sync是ReentrantLock的抽象静态内部类,继承自AQS(AbstractQueuedSynchronizer) - 抽象队列同步器,AQS中定义了锁的基本行为,AQS中用volatile修饰的state表示当前锁重入的次数。

01  Sync类图:TODO

  NonfairSync、FairSync是ReentrantLock的静态内部类,继承ReentrantLock$Sync,NonfairSync实现非公平锁,FairSync实现公平锁。

2、加锁

1、lock()

1 private final Sync sync;
2 
3 // 加锁
4 public void lock() {
5     sync.lock();
6 }

1.1、公平锁

  调用AQS的acquire方法。ReentrantLock$FairSync#lock() 核心代码:

// 加锁
final void lock() {
    acquire(1);
}

1.2、非公平锁

  通过CAS尝试获取锁(将AQS的state由0修改为1),若成功,代表当前线程获取锁资源成功;若失败调用AQS的acquire方法。ReentrantLock$NonfairSync#lock() 核心代码:

1 // 加锁
2 final void lock() {
3     // 获取锁资源,CAS 修改 AQS 的 state 属性值,,获取成功,设置当前线程
4     if (compareAndSetState(0, 1))
5         setExclusiveOwnerThread(Thread.currentThread());
6     // 获取失败,执行AQS的acquire
7     else
8         acquire(1);
9 }

2、acquire()

  acquire()方法是Sync父类AQS中的方法,AbstractQueuedSynchronizer#acquire() 核心代码:

1 // 获取锁资源
2 public final void acquire(int arg) {
3     // 尝试获取锁资源
4     if (!tryAcquire(arg) &&
5         // 当前线程为获取到锁资源,加入等待队列,同时挂起线程,等待唤醒
6         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
7         selfInterrupt();
8 } 

  1、调用tryAcquire方法:

  2、调用addWaiter方法

  3、调用acquireQueued方法

2.1、tryAcquire()

  tryAcquire()方法在FairSync、NonFairSync中均有实现,尝试获取锁资源,核心代码如下:

 1 // 公平锁 FairSync#tryAcquire() 方法
 2 protected final boolean tryAcquire(int acquires) {
 3     // 获取当前线程
 4     final Thread current = Thread.currentThread();
 5     // 获取AQS的 state
 6     int c = getState();
 7     // state == 0 当前没有线程占用锁资源
 8     if (c == 0) {
 9         // 判断是否有线程在排队,若有线程在排队,返回true
10         if (!hasQueuedPredecessors() &&
11             // 尝试抢锁
12             compareAndSetState(0, acquires)) {
13             // 无线程排队,将线程属性设置为当前线程
14             setExclusiveOwnerThread(current);
15             return true;
16         }
17     }
18     // state != 0  有线程占用锁资源
19     // 占用锁资源的线程是否为当前线程
20     else if (current == getExclusiveOwnerThread()) {
21         // state + 1
22         int nextc = c + acquires;
23         // 锁重入超出最大限制 (int的最大值),抛异常
24         if (nextc < 0)
25             throw new Error("Maximum lock count exceeded");
26         // 将 state + 1 设置给 state
27         setState(nextc);
28         // 当前线程拿到锁资源,返回true
29         return true;
30     }
31     return false;
32 }
33 
34 // 非公平锁  NonFairSync#tryAcquire() 方法
35 protected final boolean tryAcquire(int acquires) {
36     return nonfairTryAcquire(acquires);
37 }
38 
39 // 非公平锁  Sync#nonfairTryAcquire() 方法
40 final boolean nonfairTryAcquire(int acquires) {
41      // 获取当前线程
42     final Thread current = Thread.currentThread();
43     // 获取AQS的 state
44     int c = getState();
45     // 无线程占用锁资源
46     if (c == 0) {
47         // CAS 修改 state 的值,修改成功,设置线程属性为当前线程,返回占用锁资源标识
48         if (compareAndSetState(0, acquires)) {
49             setExclusiveOwnerThread(current);
50             return true;
51         }
52     }
53     // 有线程占用锁资源
54     // 占用锁资源的线程是当前线程(重入)
55     else if (current == getExclusiveOwnerThread()) {
56         // AQS 的 state + acquires
57         int nextc = c + acquires;
58         // 超出锁重入的上限(int的最大值),抛异常
59         if (nextc < 0)
60             throw new Error("Maximum lock count exceeded");
61         // 将 state + acquires 设置到 state 属性
62         setState(nextc);
63         return true;
64     }
65     return false;
66 }

  获取当前线程、AQS的state。AQS的state属性值为0,表示无线程占用锁资源,判断等待队列中是否有线程在排队,若有线程在排队,返回尝试抢锁失败标识,将线程添加进等待队列中。

  若state属性值不为0,判断持有锁资源的线程是否为当前线程,若为当前线程,AQS的state属性值 + 1,返回尝试抢锁成功标识。   公平锁与非公平锁的整体实现流程类似,唯一不同的是,AQS的state属性值为0,无线程占用锁资源时,非公平锁不会判断是否有线程在等待队列中排队,而是直接通过CAS抢锁。

2.2、addWaiter()

  为当前线程创建入队节点AbstractQueuedSynchronizer$Node,入参mode表示锁类型,在AQS的静态内部类Node中有SHARE、EXCLUSIVE两个属性,SHARE代表共享锁、EXCLUSIVE代表排它锁。  

   AbstractQueuedSynchronizer#addWaiter() 核心代码:

 1 // 等待队列的尾节点,懒加载,只能通过enq方法添加节点
 2 private transient volatile Node tail;
 3 
 4 private Node addWaiter(Node mode) {
 5     // 当前线程、获取的锁类型封装为Node对象
 6     Node node = new Node(Thread.currentThread(), mode);
 7     // 获取等待队列的尾节点
 8     Node pred = tail;
 9     // 尾节点不为null
10     if (pred != null) {
11         // 将当前节点设置为等待队列的尾节点
12         node.prev = pred;
13         if (compareAndSetTail(pred, node)) {
14             pred.next = node;
15             return node;
16         }
17     }
18     // 等待队列为空,初始化等待队列节点信息
19     enq(node);
20     // 返回当前线程节点
21     return node;
22 } 

  等待队列不为空,将当前线程封装的Node节点添加进队列尾部;若等待队列为空,先初始化等待队列,然后在将Node节点添加进队列尾部。

2.2.1、enq()

  等待队列尾节点为空时,执行enq()方法初始化等待队列,并将Node节点添加进等待队列中。

 1 private Node enq(final Node node) {
 2     for (;;) {
 3         // 获取等待队列的尾节点
 4         Node t = tail;
 5         // 等待队列为空,初始化等待队列
 6         if (t == null) {
 7             // 初始化等待队列头尾节点
 8             if (compareAndSetHead(new Node()))
 9                 tail = head;
10         } else {
11             // 当前线程的Node添加到等待队列中
12             node.prev = t;
13             if (compareAndSetTail(t, node)) {
14                 t.next = node;
15                 return t;
16             }
17         }
18     }
19 }

2.3、acquireQueued()

  当前线程是否挂起,AbstractQueuedSynchronizer#acquireQueued() 核心代码:

 1 final boolean acquireQueued(final Node node, int arg) {
 2     // 获取锁资源标识
 3     boolean failed = true;
 4     try {
 5         boolean interrupted = false;
 6         // 自旋
 7         for (;;) {
 8             // 获取当前节点的前驱节点
 9             final Node p = node.predecessor();
10             // 当前节点的前驱节点为头节点,并获取锁资源成功
11             if (p == head && tryAcquire(arg)) {
12                 // 将当前节点设置到head - 头节点
13                 setHead(node);
14                 // 原头节点的下一节点指向设置为null,GC回收
15                 p.next = null;
16                 // 设置获取锁资源成功
17                 failed = false;
18                 // 不管线程GC
19                 return interrupted;
20             }
21             // 如果当前节点不是head的下一节点,获取锁资源失败,尝试将线程挂起
22             if (shouldParkAfterFailedAcquire(p, node) &&
23                 // 线程挂起, UNSAFE.park()
24                 parkAndCheckInterrupt())
25                 interrupted = true;
26         }
27     } finally {
28         if (failed)
29             cancelAcquire(node);
30     }
31 }

  查看当前排队的Node是否是head的next, 如果是,尝试获取锁资源, 如果不是或者获取锁资源失败那么就尝试将当前Node的线程挂起(unsafe.park())。

  shouldParkAfterFailedAcquire检查并更新未成功获取锁资源的状态,返回true表示线程被挂起。

  AbstractQueuedSynchronizer#shouldParkAfterFailedAcquire() 核心代码:

 1 static final class Node {
 2     // 线程被取消
 3     static final int CANCELLED =  1;
 4     // 等待队列中存在待被唤醒的挂起线程
 5     static final int SIGNAL    = -1;
 6     // 当前线程在Condition队列中,未在AQS对列中
 7     static final int CONDITION = -2;
 8     // 解决JDK1.5的BUG。共享锁在释放资源后,若头节点为0,无法确定真的没有后继节点
 9     // 如果头节点为0,需要将头节点的状态改为 -3 ,当最新拿到锁资源的线程查看
10     // 是否有后继节点并且为当前锁为共享锁,需唤醒排队的线程。
11     static final int PROPAGATE = -3;
12 }
13 
14 // 获取锁资源失败,挂起线程
15 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
16     // 获取当前节点的上一个节点的状态
17     int ws = pred.waitStatus;
18     // 上一节点被挂起
19     if (ws == Node.SIGNAL)
20         // 返回true,挂起当前线程
21         return true;
22     if (ws > 0) {
23         // 上一节点被取消,获取最近的线程挂起节点,
24         // 并将当前节点的上一节点指向最近的线程挂起节点
25         do {
26             node.prev = pred = pred.prev;
27         } while (pred.waitStatus > 0);
28         // 最近线程挂起节点的下一节点指向当前节点
29         pred.next = node;
30     } else {
31         // 上一节点状态小于等于0,存在线程处于等待状态,但未被挂起的场景
32         // 通过CAS将处于等待的线程挂起,避免在挂起前节点获取到锁资源
33         compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
34     }
35     //  返回true,不挂起当前线程
36     return false;
37 }

  在挂起线程前,确认当前节点的上一个节点的状态。若为1,代表是取消的节点,不能挂起;若为-1,代表后续节点中有挂起的线程;若为-2 (线程在等待队列 - Condition队列中)、-3 (避免线程无法唤醒的一个状态),需要将状态改为-1之后,才能挂起当前线程。

3、释放锁

1、unlock()

  释放锁,ReentrantLock#unlock() 核心代码:

// 释放锁
public void unlock() {
    sync.release(1);
}

  unlock方法实际调用的是AQS的release方法,AbstractQueuedSynchronizer#release() 核心代码:

 1 // 等待队列的头节点,懒加载,通过setHead方法初始化
 2 private transient volatile Node head;
 3 
 4 // 释放锁
 5 public final boolean release(int arg) {
 6     // 当前线程释放锁资源的计数值
 7     if (tryRelease(arg)) {
 8         // 当前线程玩去释放锁资源,获取等待队列头节点
 9         Node h = head;
10         if (h != null && h.waitStatus != 0)
11             // 唤醒等待队列中待唤醒的节点
12             unparkSuccessor(h);
13         // 完全释放锁资源
14         return true;
15     }
16     // 当前线程未完全释放锁资源
17     return false;
18 } 

1.1、tryRelease()

  释放锁,Reenttrant$Sync#tryRelease()的核心代码

 1 // 释放锁
 2 protected final boolean tryRelease(int releases) {
 3     // 修改 AQS 的 state 
 4     int c = getState() - releases;
 5     // 当前线程不是持有锁的线程,抛出异常
 6     if (Thread.currentThread() != getExclusiveOwnerThread())
 7         throw new IllegalMonitorStateException();
 8     // 是否成功的将锁资源完全释放标识 (state == 0)
 9     boolean free = false;
10     // 锁资源完全释放
11     if (c == 0) {
12         // 修改标识
13         free = true;
14         // 将占用锁资源的属性设置为null
15         setExclusiveOwnerThread(null);
16     }
17     // state赋值
18     setState(c);
19     // 返回true表示当前线程完全释放锁资源;
20     // 返回false标识当前线程是由锁资源,持有计数值减少
21     return free;
22 }
   

标签:分析,Node,队列,ReentrantLock,state,源码,线程,节点
From: https://www.cnblogs.com/RunningSnails/p/17380623.html

相关文章

  • 记一次 .NET 某车零件MES系统 登录异常分析
    一:背景1.讲故事这个案例有点特殊,以前dump分析都是和软件工程师打交道,这次和非业内人士交流,隔行如隔山,从指导dump怎么抓到问题解决,需要一个强大的耐心。前几天有位朋友在微信上找到我,说他们公司采购的MES系统登录的时候出现了异常,让我帮忙看一下,我在想解铃还须系铃人,怎么的也不......
  • 高通量测序分析工具Bedtools使用介绍
    Bedtools是处理基因组信息分析的强大工具集合,其主要功能如下:bedtools:flexibletoolsforgenomearithmeticandDNAsequenceanalysis.usage:bedtools<subcommand>[options]Thebedtoolssub-commandsinclude:[Genomearithmetic]intersectFindove......
  • 基因共表达聚类分析及可视化
    共表达基因的寻找是转录组分析的一个部分,样品多可以使用WGCNA,样品少可直接通过聚类分析如K-means、K-medoids(比K-means更稳定)或Hcluster或设定pearsoncorrelation阈值来选择共表达基因。下面将实战演示K-means、K-medoids聚类操作和常见问题:如何聚类分析,如何确定合适的cluster......
  • 富集分析一网打进
    一是基于筛选的差异基因,采用超几何检验判断上调或下调基因在哪些GO或KEGG或其它定义的通路富集。假设背景基因数目为m,背景基因中某一通路pathway中注释的基因有n个;上调基因有k个,上调基因中落于通路pathway的数目为l。简单来讲就是比较l/k是否显著高于n/m,即上调基因中落在通路pathwa......
  • 转录组分析的正确姿势
    转录组的测序分析也相对成熟,从RNA提取、构建文库、上机测序再到结果解析既可以自己完成,又可以在专业公司进行。概括来看转录组的分析流程比较简单,序列比对-转录本拼接(可选)-表达定量-差异基因-功能富集-定制分析。整个环节清晰流畅,可以作为最开始接触高通量测序学习最合适的技术......
  • GSEA富集分析 - 界面操作
    GSEA定义GeneSetEnrichmentAnalysis(基因集富集分析)用来评估一个预先定义的基因集的基因在与表型相关度排序的基因表中的分布趋势,从而判断其对表型的贡献。其输入数据包含两部分,一是已知功能的基因集(可以是GO注释、MsigDB的注释或其它符合格式的基因集定义),一是表达矩阵,软件......
  • R语言学习 - 非参数法生存分析
    生存分析指根据试验或调查得到的数据对生物或人的生存时间进行分析和推断,研究生存时间和结局与众多影响因素间关系及其程度大小的方法,也称生存率分析或存活率分析。常用于肿瘤等疾病的标志物筛选、疗效及预后的考核。简单地说,比较两组或多组人群随着时间的延续,存活个体的比例变化......
  • java基于springboot+vue非前后端分离的学生成绩管理系统、学生信息管理系统,附源码+数
    1、项目介绍java基于springboot+vue非前后端分离的学生成绩管理系统、学生信息管理系统。本文首先介绍了学生成绩管理的技术发展背景与发展现状,然后遵循软件常规开发流程,首先针对系统选取适用的语言和开发平台,根据需求分析制定模块并设计数据库结构,再根据系统总体功能模块的设计......
  • JS逆向 -- 某平台登录算法分析(RSA加密)
    一、输入账号密码,进行抓包二、F12打开开发者工具,抓包分析,password被加密了三、全局搜索password关键字,挨个分析,在箭头标记处找到了关键代码四、局部搜索,定位加密的关键点,通过JSEncrypt,setPublicKey等关键字分析是RSA加密五、代码编写1、调用RSA加密的基本代码编写functionaiyou(pw......
  • 《花雕学AI》讯飞星火认知大模型的特点和优势,与ChatGPT的对比分析
    引言:人工智能是当今科技领域的热门话题,自然语言处理是人工智能的重要分支。自然语言处理的目标是让计算机能够理解和生成自然语言,实现人机交互和智能服务。近年来,随着深度学习的发展,自然语言处理领域出现了许多创新和突破,其中最具代表性的就是预训练语言模型。预训练语言模型是一......