多线程JUC 第2季 ReentranctLock实现加锁和解锁过程

一 ReentranctLock

1.1 ReentranctLock

1.ReentrantLock 意思为可重入锁,指的是一个线程能够对一个临界资源重复加锁。ReentrantLock内部实现依赖于AQS。

1.2 ReentranctLock的结构组成

ReentrantLock有三个内部类:
Sync:继承自AQS的同步控制基础。
NonfairSync:Sync的非公平版本实现
FairSync:Sync的公平版本实现
ReentrantLock默认实例化的非公平锁NonfairSync。可以通过传递true,来实例化公平锁FairSync

1.3 ReentranctLock的加锁和解锁流程

1)FairSync加锁过程:
1.调用lock()方法即可加锁。
2.加锁时先tryAcquire(1)尝试获取锁:
2.1 如果获取成功,那么把当前线程设置为工作线程,结束加锁流程
2.2 如果获取失败,那么把当前线程添加到CLH队列的队尾进行等待。
2)FairSync解锁过程:
1.调用unlock()方法即可解锁。注意,该方法一定要放在finally块中进行调用!
2.解锁时调用tryRelease(arg)方法
2.1如果成功,唤醒等待队列中的线程进行工作
2.2如果失败,返回false

3)NonFairSync加锁解锁过程:
1.NonFairSync会在调用lock()时,直接尝试把state设置为1:
1.1如果失败,会调用nonfairTryAcquire()获取锁,而nonfairTryAcquire()在获取锁的时候,也不会顾及CLH队列中是否有线程在等待获取锁,从而实现非公平加锁。
1.2如果成功,直接获取锁,执行业务。
4)解锁过程和FairSync一致。

二 ReentranctLock源码分析

2.1 公平锁

2.1.1 lock方法

1.看到lock()方法其实就是调用Sync的lock()方法,而Sync的Lock有NonFairSync和FairSync两种实现。

2.这边先看FairSync的实现,该方法的参数1,实际上即是state的值,1表示加锁。继续看acquire()方法:

3.acquire()调用了三个方法,分别是:

   tryAcquire():尝试以独占模式获取锁

   addWaiter():为当前线程和给定模式创建并排队节点。

    acquireQueued():以独占不中断模式获取已在队列中的线程

如下图所示:

 3.1 tryAcquire()方法

protected final boolean tryAcquire(int acquires) {// 获取当前线程final Thread current = Thread.currentThread();// 获取state的值int c = getState();// state等于0,即共享资源没有被加锁if (c == 0) {/*先看队列中有没有线程在等待获取锁,如果没有那就设置当前线程就是工作线程如果有就返回false,此处体现了公平锁特性*/if (!hasQueuedPredecessors() &&compareAndSetState(0, acquires)) {// 设置当前线程为独占线程setExclusiveOwnerThread(current);return true;}}// 如果当前工作线程是该线程,那么可以重复获取锁,并将state的值+1,此处体现了ReentrantLock是可重入锁else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;// int有最大值,超过最大值后,state会变成负数,因此加了这个判断if (nextc < 0)throw new Error("Maximum lock count exceeded");setState(nextc);return true;}// 共享资源被锁定,返回falsereturn false;
}

这个方法的第一个if判断,提现了获取锁是公平的;第二个则体现是ReentrantLock是可重入锁。

看一下hasQueuedPredecessors()方法,

hasQueuedPredecessors():代码逻辑如下:

public final boolean hasQueuedPredecessors() {Node t = tail; Node h = head;Node s;// 头结点不等于尾节点,并且,(头结点的next不为null,或者next的线程不是当前线程)return h != t &&((s = h.next) == null || s.thread != Thread.currentThread());
}

3.2 addWaiter():

private Node addWaiter(Node mode) {// 把当前线程作为入参创建一个Node节点Node node = new Node(Thread.currentThread(), mode);// Try the fast path of enq; backup to full enq on failureNode pred = tail;// 如果队列的尾节点不为null,把node添加到队尾if (pred != null) {node.prev = pred;// cas设置尾节点if (compareAndSetTail(pred, node)) {pred.next = node;return node;}}// 将节点插入队列,如果队列为空则初始化队列 enq(node);return node;
}

addWaiter(),就是把当前线程添加到队列中。 其中的enq()是在快速添加node到队尾失败才会调用,而这个方法也是添加node到队列中。

acquireQueued():

final boolean acquireQueued(final Node node, int arg) {// 标记是否成功拿到资源boolean failed = true;try {// 标记等待过程中是否中断过boolean interrupted = false;// 开始自旋,要么获取锁,要么中断for (;;) {// 获取当前节点的前节点final Node p = node.predecessor();// 如果前节点是头结点(工作节点)并且tryAcquire()成功if (p == head && tryAcquire(arg)) {// 把node设置为头结点setHead(node);p.next = null; // help GCfailed = false;return interrupted;}// 靠前驱节点判断当前线程是否应该被阻塞if (shouldParkAfterFailedAcquire(p, node) &&// 阻塞当前节点parkAndCheckInterrupt())interrupted = true;}} finally {// 如果执行失败,则把这个节点状态设置为取消状态if (failed)cancelAcquire(node);}
}

这边多了shouldParkAfterFailedAcquire()和parkAndCheckInterrupt()方法。

shouldParkAfterFailedAcquire():靠前驱节点判断当前线程是否应该被阻塞

// 靠前驱节点判断当前线程是否应该被阻塞
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {// 获取头结点的节点状态int ws = pred.waitStatus;// 说明头结点处于唤醒状态if (ws == Node.SIGNAL)return true;// 通过枚举值我们知道waitStatus>0是取消状态if (ws > 0) {do {// 循环向前查找取消节点,把取消节点从队列中剔除node.prev = pred = pred.prev;} while (pred.waitStatus > 0);pred.next = node;} else {// 设置前任节点等待状态为SIGNALcompareAndSetWaitStatus(pred, ws, Node.SIGNAL);}return false;
}

parkAndCheckInterrupt():阻塞当前节点

private final boolean parkAndCheckInterrupt() {// 阻塞当前线程LockSupport.park(this);// 当前线程被唤醒后,返回线程的中断状态,然后重置中断状态return Thread.interrupted();
}

2.1.2 unlock方法

1.在调用lock.unlock()时候,内部调用的是Sync的release()方法,源码:

2.release(): 

public final boolean release(int arg) {// 释放锁if (tryRelease(arg)) {Node h = head;// 如果当前头结点不为null,并且waitStatus不是0(无锁状态)if (h != null && h.waitStatus != 0)// 唤醒下一个线程开展业务unparkSuccessor(h);return true;}return false;
}

release()会调用tryRelease()方法:

  • 如果返回tryRelease()false,那么release()直接返回false
  • 如果返回true,进行判断后,会唤醒队列中下一个线程进行工作

3.tryRelease():

protected final boolean tryRelease(int releases) {// 获取state的值,并且减去1(releases等于1)int c = getState() - releases;// 判断当前线程是不是工作线程,如果不是,抛出异常if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();// 共享资源是否空闲的标记boolean free = false;// 如果state为0(空闲)if (c == 0) {// 设置空闲标记为true,并把当前工作线程设置为nullfree = true;setExclusiveOwnerThread(null);}// 设置state等于0setState(c);return free;
}

tryRelease()会释放一次锁,不过ReentrantLock是可重入锁,因此释放一次后,共享资源不一定会处于空闲状态。

4.unparkSuccessor():

private void unparkSuccessor(Node node) {// 获取头结点waitStatusint ws = node.waitStatus;if (ws < 0)compareAndSetWaitStatus(node, ws, 0);// 获取当前节点的下一个节点Node s = node.next;// 如果下个节点是null或者下个节点被cancelled,就找到队列最开始的非cancelled的节点if (s == null || s.waitStatus > 0) {s = null;// 就从尾部节点开始找,到队首,找到队列第一个waitStatus<0的节点。for (Node t = tail; t != null && t != node; t = t.prev)if (t.waitStatus <= 0)s = t;}// 如果当前节点的下个节点不为空,而且状态<=0,就把当前节点unparkif (s != null)LockSupport.unpark(s.thread);
}

unparkSuccessor()就是唤醒线程进行工作。只有当唤醒下一个线程后,release()方法才会返回true。

2.2 非公平锁

2.2.1 非公平锁

对比FairSync的源码,会发现两者的区别就是在于lock方法:

1.非公平锁: 不公平锁加锁时,会直接尝试设置state的值。

final void lock() {// 第一步直接尝试设置state的值(不公平啊)if (compareAndSetState(0, 1))// 设置成功,设置当前线程为工作线程setExclusiveOwnerThread(Thread.currentThread());else// 设置失败,再去尝试获取锁acquire(1);
}

 2.在看accuire方法:可以看到和FairSync的acquire()方法一样,唯一不同的是tryAcquire()的不同;

    public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}

3.tryAcquire():tryAcquire()调用的是nonfairTryAcquire(),

4.nonfairTryAcquire():

对比FairSync的tryAcquire()方法和nonfair的TryAcquire()方法,可以看出唯一的区别就是FairSync的tryAcquire()方法多了一个hasQueuedPredecessors()的判断,这个判断是判断CLH队列中有没有线程在等待获取资源。而非公平锁就不管等待队列是否有等待线程,直接去尝试获取资源(不公平)。ReentrantLock加锁和解锁-CSDN博客

final boolean nonfairTryAcquire(int acquires) {// 获取当前线程final Thread current = Thread.currentThread();int c = getState();// 如果共享资源处于空闲状态if (c == 0) {// 尝试设置直接设置state(不管队列中是否有排队的线程直接就插队,不公平啊)if (compareAndSetState(0, acquires)) {// 设置state成功,就把当前线程设置为工作线程setExclusiveOwnerThread(current);return true;}}// 如果当前线程就是工作线程,那么可以重复加锁else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/134422.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【数据可视化】动态条形图Python代码实现

使用 Python 中的 bar_chart_race_cn 库创建动态条形图 前言 数据可视化在今天的数据分析和传达信息中起着至关重要的作用。动态条形图是一种强大的数据可视化工具&#xff0c;可以帮助我们展示随时间变化的数据趋势。本文将介绍如何使用 Python 编程语言中的 bar_chart_race…

数据库安全(Mysql,Hadoop,Redis)

MySQL Mysql 身份认证绕过漏洞&#xff08;CVE-2012-2122&#xff09; 当连接MariaDB/MySQL时&#xff0c;输入的密码会与期望的正确密码比较&#xff0c;由于不正确的处理&#xff0c;会导致即便是memcmp()返回一个非零值&#xff0c;也会使MySQL认为两个密码是相同的。也就…

flask要点与坑

简介 Flask是一个用Python编写的Web应用程序框架&#xff0c;该框架简单易用、模块化、灵活性高。 该笔记主要记录Flask的关键要点和容易踩坑的地方 Flask 日志配置 Flask 中的自带logger模块&#xff08;也是python自带的模块&#xff09;&#xff0c;通过简单配置可以实现…

周易算卦流程c++实现

代码 #include<iostream> using namespace std; #include<vector> #include<cstdlib> #include<ctime> #include<Windows.h>int huaYiXiangLiang(int all, int& left) {Sleep(3000);srand(time(0));left rand() % all 1;while (true) {if…

【React】React入门

目录 一、何为React二、React与传统MVC的关系三、React的特性1、声明式编程①、实现标记地图 2、高效灵活3、组件式开发(Component)①、函数式组件②、类组件&#xff08;有状态组件&#xff09;③、一个组件该有的特点 4、单向式响应的数据流 四、虚拟DOM1、传统DOM更新①、举…

【ubuntu】修改系统及硬件时间

Linux系统时间分为两种&#xff1a;系统时间&#xff08;S有stem Clock&#xff09;和硬件&#xff08;Realtime Clock&#xff0c;简称RTC&#xff09;时间。 上网找了好多教程&#xff0c;每次修改完后&#xff0c;不到几秒钟&#xff0c;时间又恢复成之前的时间了。 -------…

pytorch代码实现之动态卷积模块ODConv

ODConv动态卷积模块 ODConv可以视作CondConv的延续&#xff0c;将CondConv中一个维度上的动态特性进行了扩展&#xff0c;同时了考虑了空域、输入通道、输出通道等维度上的动态性&#xff0c;故称之为全维度动态卷积。ODConv通过并行策略采用多维注意力机制沿核空间的四个维度…

VUE之proxy配置实现跨域

什么是跨域 要了解跨域&#xff0c;首先得知道浏览器的同源策略。 同源策略&#xff1a;是由Netscape提出的一个安全策略&#xff0c;能够阻挡恶意文档&#xff0c;保护本地数据。它能限制一个源的文档或脚本对另一个源的交互&#xff0c;使得其它源的文档或脚本&#xff0c;…

火山引擎 ByteHouse:ClickHouse 如何保证海量数据一致性

背景 ClickHouse是一个开源的OLAP引擎&#xff0c;不仅被全球开发者广泛使用&#xff0c;在字节各个应用场景中也可以看到它的身影。基于高性能、分布式特点&#xff0c;ClickHouse可以满足大规模数据的分析和查询需求&#xff0c;因此字节研发团队以开源ClickHouse为基础&…

docker 已经配置了国内镜像源,但是拉取镜像速度还是很慢(gcr.io、quay.io、ghcr.io)

前言 国内用户在使用 docker 时&#xff0c;想必都遇到过镜像拉取慢的问题&#xff0c;那是因为 docker 默认指向的镜像下载地址是 https://hub.docker.com&#xff0c;服务器在国外。 网上有关配置 docker 国内镜像源的教程很多&#xff0c;像 腾讯、阿里、网易 等等都会提供…

前后端跨域请求问题解决方法

如图&#xff1a; 1.在config配置包中创建一个CorsConfig配置类 2.将下面代码复制到这个类中即可 import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfigurati…

华为云云耀云服务器L实例评测|服务器反挖矿防护指南

前言 本文为华为云云耀云服务器L实例测评文章&#xff0c;测评内容是 云耀云服务器L实例 反挖矿防护指南 系统配置&#xff1a;2核2G 3M CentOS7.9 之前的文章中『一文教你如何防御数据库恶意攻击』&#xff0c;我们讲到黑客如何通过攻击数据库来获取权限&#xff0c;以及我们…

Java面试常用函数

1. charAt() 方法用于返回字符串指定索引处的字符。索引范围为从 0 到 length() - 1。 map.getOrDefault(num, 0) :如果map存在num这个key&#xff0c;则返回num对应的value&#xff0c;否则返回0. Arrays.sort(nums); 数组排序 Arrays.asList("a","b",&q…

详细解释HiveSQL执行计划

一、前言 Hive SQL的执行计划描述SQL实际执行的整体轮廓&#xff0c;通过执行计划能了解SQL程序在转换成相应计算引擎的执行逻辑&#xff0c;掌握了执行逻辑也就能更好地把握程序出现的瓶颈点&#xff0c;从而能够实现更有针对性的优化。此外还能帮助开发者识别看似等价的SQL其…

java 封装一个将String类型转Long类型的函数

Long是一种超大类型的数字变量类型 但java无法直接生成这种数据 但我们可以封装一个函数 public Long getuniid(String number) {Long longNumber Long.parseLong(number);return longNumber; }这样 我们就可以传入一个字符串 然后将其转换为 long 然后我们调用这个函数 Sys…

向日葵无法连接服务器(无法登录)

最近在使用向日葵过程中&#xff0c;突然就不能登录向日葵了&#xff0c;网上查了各种解决方案&#xff0c;比如说防火墙是不是把向日葵给拦截了&#xff1f;更换不同的版本等等&#xff0c;都无法解决&#xff0c;最后突然想到是不是电脑对向日葵原安装目录限制了&#xff1f;…

虚拟化技术:深入浅出

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

PY32F003F18之输入捕获

输入捕获是定时器的功能之一&#xff0c;配合外部引脚&#xff0c;捕获脉宽时间或采集周期。 CPU中的定时器最基本的功能就是计数功能&#xff0c;其次是输入捕获(IC)&#xff0c;再次就是比较输出(OC)&#xff0c;还有就是使用引脚对外部时钟进行计数&#xff0c;触发信号捕捉…

关于阿里云服务器Ubuntu编译jdk8中遇到的坑及解决方案

关于阿里云服务器Ubuntu系统安装jdk8中遇到的坑及解决方案 记录一下困扰了很多天、到处查资料最后终于成功安装的过程 关于阿里云服务器无法登录的问题 基本反馈是这样的&#xff1a; 如果你添加了ip之后仍然登不进去&#xff0c;有一种方法是直接从第三个选项进去登录之后修…

JWT安全

文章目录 JWT是什么&#xff1f;为什么要使用JWT&#xff1f;JWT的数据结构JWT的工作过程 JWT是什么&#xff1f; JSON Web Token (JWT)是一个开放标准(RFC 7519)&#xff0c;它定义了一种紧凑的、自包含的方式&#xff0c;用于作为JSON对象在各方之间安全地传输信息。 JWT全称…