ReentrantLock 分析

带着疑问去分析

  1. ReentrantLock是如何实现锁管理的。
  2. ReentrantLock是如何实现重入的。
  3. ReentrantLock是如何实现公平锁与非公平锁。
  4. ReentantLock的公平锁为什么一般情况下性能都比公平锁查。

ReentrantLock数据结构

ReentrantLock的底层是借助于AbstractQueuedSynchronizer实现的,所以其数据结构依赖于AbstractQueuedSynchronizer的数据结构。

AQS的数据结构 如下图:

616953-20160403170136176-573839888.png

ReentrantLock源码分析

类的继承关系

public class ReentrantLock implements Lock, java.io.Serializable

说明:ReentrantLock实现了Lock接口,Lock接口中定义了lock与unlock操作,并且还存在newCondition方法,表示生成一个条件。

类的内部类

ReentrantLock总共有三个内部类,并且三个内部类是紧密相关的,下面先看三个类的关系:

616953-20160412160216863-51025450.png

说明:

  • ReentrantLock类内部总共存在Sync、NonfairSync、FairSync三个类,NonfairSync与FairSync类继承Sync类,Sync类继承自AQS抽象类。
  • ReentrantLock是通过构造方法实现公平锁与非公平锁定义的。
public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();
}

Sync类

Sync类源码如下:(只列出主要方法)

/*** 获取锁 由于公平锁和非公平锁  获取锁的动作 不一样,所以留着让子类实现更好。*/
abstract void lock();/*** 非公平模式下尝试获取锁。两种情况:*   1. 锁没被占用:*   (1). CAS将当前锁状态加1*   (2). 将当前线程设置为锁的独享者。*  2. 锁被占用*   (1). 如果锁占用者是当前线程 ,则将线程状态加+1 ,通过这个线程状态 就可以知道重入次数*/
final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {if (compareAndSetState(0, acquires)) {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;
}/****  第一步:依次减去释放量(releases变量 这里都是1),*  第二步:如果全部释放(state),则将锁占用者置为null,表示释放锁。* @param releases* @return*/
protected final boolean tryRelease(int releases) {int c = getState() - releases;if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;if (c == 0) {free = true;setExclusiveOwnerThread(null);}setState(c);return free;
}/***  判断当前线程是否是锁的拥有者* @return*/
protected final boolean isHeldExclusively() {return getExclusiveOwnerThread() == Thread.currentThread();
}/*** 查看当前线程重入次数* @return*/
final int getHoldCount() {return isHeldExclusively() ? getState() : 0;
}/*** 当前锁是否被占用* @return*/
final boolean isLocked() {return getState() != 0;
}

说明:Sync类存在如下方法和作用如下。
616953-20160412165914863-1444593791.png

NonfairSync类

NonfairSync类继承了Sync类,表示采用非公平策略获取锁,其实现了Sync类中抽象的lock方法,源码如下:

static final class NonfairSync extends Sync {private static final long serialVersionUID = 7316153563782823691L;/*** 1. 英文版 注释:* Performs lock.  Try immediate barge, backing up to normal* acquire on failure.** 我的谬论:*  barge 在英文中是: 蛮不讲理地闯入或打扰某事物 、闯入之意。( 用在此场景,个人感觉很好呀 ^_^ )*  加锁过程:当一个线程调用lock方法时,直接尝试加锁,如果加锁失败,再进行正常的获取。*  从这里可以看出 作者认为 ReentrantLock更多的时候,锁的争抢并不激烈,大多时候第一次的barge就能获取锁,所以作者才会首先进行barge,失败再尝试正常获取。*/final void lock() {if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());elseacquire(1);}/*** lock 中的 acquire(1) 代码段可能会调用此方法 , 此方法的作用主要是为了 获取独占锁 , 获取失败的线程将会进入等待队列* @param acquires* @return*/protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);}
}

说明: 从lock方法的源码可知,每一次都尝试获取锁,而并不会按照公平等待的原则进行等待,让等待时间最久的线程获得锁。

FairSync类

FairSync类也继承了Sync类,表示采用公平策略获取锁,其 实现了Sync类中的抽象lock方法,源码如下:

 static final class FairSync extends Sync {private static final long serialVersionUID = -3000897897090466540L;final void lock() {acquire(1);}/*** 尝试获取独占锁. 两种情况:*  1. 锁未被占用(state = 0 )*    如果等待队列为空 或当前线程时等待队列首个出队线程 则尝试获取锁.*  2. 锁被占用(state != 0)*    校验当前线程是否已经拥有独占锁,如果是,则记录重入次数。* @param acquires* @return*/protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {if (!hasQueuedPredecessors() &&compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0)throw new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}}

说明:跟踪lock的源码可知,当一个线程尝试获取锁时,总是会首先判断sync队列(AbstractQueuedSynchronizer中的数据结构)是否有等待时间更长的线程。如果存在,则将线程加入到等待队列的尾部,实现了公平获取原则。这是和Nonfair最大的区别,Nonfair每一次都会尝试去获取资源,如果此时该资源恰好被释放,这就造成了不公平的现象,当获取不成功,再加入到队列尾部。

最后

我们可以通过下图来深刻的认识公平性和AQS的获取过程。

非公平的,或者说默认的获取方式如下图所示:
Untitled.png

对于状态的获取,可以快速的通过tryAcquire的成功,也就是黄色的Fast路线,也可以由于tryAcquire的失败,构造节点,进入sync队列中排序后再次获取。因此可以理解为Fast就是一个快速通道,当例子中的线程释放锁之后,快速的通过Fast通道再次获取锁,就算当前sync队列中有排队等待的线程也会被忽略。这种模式,可以保证进入和退出锁的吞吐量,但是sync队列中过早排队的线程会一直处于阻塞状态,造成“饥饿”场景。

而公平锁,就是在tryAcquire的调用中顾及当前sync队列中的等待节点(废弃fast通道),也就是任意请求都需要按照sync队列中既有的顺序进行,先到先得。这样很好的确保了公平性。

回答之前的疑惑

  1. ReentrantLock是如何实现锁管理的。
    加锁:
int c = getState();
if (c == 0) {if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}
}

释放锁:

int c = getState() - releases;boolean free = false;
if (c == 0) {free = true;setExclusiveOwnerThread(null);
}
setState(c);return free;

state状态量的值如果大于0,则表示锁被占用,否则以CAS 乐观锁的形式将state赋值,从而表示锁被占用。释放锁的过程其实就是state递减为0的过程,此时不需要同步任何同步手段,是因为只有之前lock成功的线程才能调用unlock方法,所以无需任何同步手段。

2.ReentrantLock是如何实现重入的。
通过state的值记录重入次数,释放的时候也是通过state值递减来记录是否退出所有的重入。

3.ReentrantLock是如何实现公平锁与非公平锁?
是通过“barge”的形式,快速抢占,抢不到再遵守秩序排队。

4.ReentantLock的公平锁为什么一般情况下性能都比公平锁查。

在恢复一个被挂起的线程与该线程真正运行之间存在着严重的延迟。

假设线程A持有一个锁,并且线程B请求这个锁。由于所被A持有,因此B将被挂起。当A释放锁时,B将被唤醒,因此B会再次尝试获取这个锁。与此同时,如果线程C也请求这个锁,那么C很可能会在B被完全唤醒之前获得、使用以及释放这个锁。这样就是一个双赢的局面:B获得锁的时刻并没有推迟,C更早的获得了锁,做完了自己要做的事,并且吞吐量也提高了。

频繁的线程切换和线程唤醒睡眠动作将会消耗系统资源:

当一个线程尝试加锁的时候,如果发现锁被抢占,会睡眠等待唤醒再次尝试加锁。如果锁竞争比较激烈,会造成频繁的线程切换 线程睡眠与唤醒动作。

5.我们是否应该抛弃synchronized ?

虽然 ReentrantLock 是个非常动人的实现,相对 synchronized 来说,它有一些重要的优势,但是我认为急于把 synchronized 视若敝屣,绝对是个严重的错误。 java.util.concurrent.lock 中的锁定类是用于高级用户和高级情况的工具 。一般来说,除非您对 Lock 的某个高级特性有明确的需要,或者有明确的证据(而不是仅仅是怀疑)表明在特定情况下,同步已经成为可伸缩性的瓶颈,否则还是应当继续使用 synchronized。

为什么我在一个显然“更好的”实现的使用上主张保守呢?因为对于 java.util.concurrent.lock 中的锁定类来说,synchronized 仍然有一些优势。比如,在使用 synchronized 的时候,不能忘记释放锁;在退出 synchronized 块时,JVM 会为您做这件事。您很容易忘记用 finally 块释放锁,这对程序非常有害。您的程序能够通过测试,但会在实际工作中出现死锁,那时会很难指出原因(这也是为什么根本不让初级开发人员使用 Lock 的一个好理由。)

另一个原因是因为,当 JVM 用 synchronized 管理锁定请求和释放时,JVM 在生成线程转储时能够包括锁定信息。这些对调试非常有价值,因为它们能标识死锁或者其他异常行为的来源。 Lock 类只是普通的类,JVM 不知道具体哪个线程拥有 Lock 对象。而且,几乎每个开发人员都熟悉 synchronized,它可以在 JVM 的所有版本中工作。在 JDK 5.0 成为标准(从现在开始可能需要两年)之前,使用 Lock 类将意味着要利用的特性不是每个 JVM 都有的,而且不是每个开发人员都熟悉的。

6.什么时候选择用 ReentrantLock 代替 synchronized?

结构锁、多个条件变量或者锁投票。 ReentrantLock 还具有可伸缩性的好处,应当在高度争用的情况下使用它,但是请记住,大多数 synchronized 块几乎从来没有出现过争用,所以可以把高度争用放在一边。我建议用 synchronized 开发,直到确实证明 synchronized 不合适,而不要仅仅是假设如果使用 ReentrantLock “性能会更好”。请记住,这些是供高级用户使用的高级工具。(而且,真正的高级用户喜欢选择能够找到的最简单工具,直到他们认为简单的工具不适用为止。)。一如既往,首先要把事情做好,然后再考虑是不是有必要做得更快。

Lock 框架是同步的兼容替代品,它提供了 synchronized 没有提供的许多特性,它的实现在争用下提供了更好的性能。但是,这些明显存在的好处,还不足以成为用 ReentrantLock 代替 synchronized 的理由。相反,应当根据您是否 需要 ReentrantLock 的能力来作出选择。大多数情况下,您不应当选择它 —— synchronized 工作得很好,可以在所有 JVM 上工作,更多的开发人员了解它,而且不太容易出错。只有在真正需要 Lock 的时候才用它。在这些情况下,您会很高兴拥有这款工具。

参看博文

  • ReentrantLock(重入锁)以及公平性
  • 【JUC】JDK1.8源码分析之ReentrantLock(三)
  • ReentrantLock实现原理深入探究
  • Java中的锁
  • 公平锁与非公平锁
  • java并发库 Lock 公平锁和非公平锁

转载于:https://www.cnblogs.com/boothsun/p/7955907.html

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

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

相关文章

v4l2 框架下如何设置分辨率_Linux下如何进行FTP设置

目录&#xff1a; 一、Redhat/CentOS安装vsftp软件二、Ubuntu/Debian安装vsftp软件一、Redhat/CentOS安装vsftp软件1.更新yum源yum update -y2.安装vsftpyum install vsftpd -y3.修改配置文件vi /etc/vsftpd/vsftpd.conf # 原有初始配置 local_umask022 dirmessage_enableYES x…

2017-2018-1 20155213 《信息安全系统设计基础》第十一周学习总结

2017-2018-1 20155213 《信息安全系统设计基础》第十一周学习总结 【学习内容:第九章——虚拟存储器】 一、课本内容梳理 1.虚拟存储器 作用&#xff1a; 将主存看作是一个存储在磁盘上的地址空间的高速缓存&#xff0c;在主存中只保护活动的区域&#xff0c;并根据需要在磁盘和…

vscode弹出cmd_先看看 VS Code Extension 知识点,再写个 VS Code 扩展玩玩

TL;DR文章篇幅有点长 &#xff0c;可以先收藏再看 。要是想直接看看怎么写一个扩展&#xff0c;直接去第二部分 &#xff0c;或者直接去github看源码 。第一部分 --- Extension 知识点一、扩展的启动如何保证性能 --- 扩展激活&#xff08;Extension Activation&#xff09; 我…

webp转换gif_用 WebP 创建尺寸更小、细节更丰富的图片,以此来提高网站的速度...

WebP 文件格式是一种基于 RIFF(资源互换文件格式)的文档格式。WebP 是 2010 年 Google 开发的一种图片格式&#xff0c;它为网页上的图片提供了卓越的无损和有损压缩。网站开发者们可以使用 WebP 来创建尺寸更小、细节更丰富的图片&#xff0c;以此来提高网站的速度。更快的加载…

python 如何判断excel单元格为空_如何用python处理excel(二)

读取excelimport xlrdworkbookxlrd.open_workbook(rC:\Users\Desktop\hebing\学生登记表.xls)sheetworkbook.sheet_by_index(0)#根据序列号来打开某一个sheetrowsheet.nrows#将excel的行数赋值给变量colsheet.ncols#将excel的列数赋值给变量print(sheet.cell_value(1,0))#打印出…

web前端到底是什么?有前途吗

web前端到底是什么&#xff1f; 某货&#xff1a; “前几年前端开发人员鱼目混杂&#xff0c;技术参差不齐&#xff0c;相对学习起来不规范&#xff0c;导致> 前端开发人员聚集&#xff0c;所以现在前端工种和工资还是没得到普遍重视&#xff0c;但近2年来&#xff0c;> …

此加载项为此计算机的所有用户安装_MDI Jade 6.5软件安装教程

软件下载▼关注微信公众号&#xff1a;贵州永航科技回复Jade即可获得软件安装包下载地址以及详细安装教程更多软件安装教程可点击菜单栏获取软件介绍MDI Jade是一款专门用于XRD分析的软件&#xff0c;XRD分析就是X射线衍射分析&#xff0c;MDI Jade通过对材料进行X射线衍射&…

java 线程定时器_Java线程之Timer定时器

定时/计划功能主要使用的就是Timer对象&#xff0c;它在内部还是使用多线程的方式进行处理&#xff0c;所以它和线程技术还是有非常大的关联。Timer类主要作用就是设置计划任务&#xff0c;但封装任务的类却是TimerTask类。TimerTask类是一个抽象类。执行任务的时间晚于当前时间…

vscode 写vue 没有js提示_如何用VSCode实现一个vue.js项目?

1,新建项目打开Visual studio code打开一个你想要创建项目的文件夹打开集成终端&#xff1a;查看 –> 集成终端 或者直接按 ctrl\ 如果没有安装vue-cli&#xff0c;在终端输入&#xff1a;npm install \-g vue-cli全局安装vue-cli然后新建项目vue init webpack projectNamep…

python有没有类似unity3d_像web一样使用python

使用传统的web开发技术,也就是htmljs,然后搭配一个后端语言,已经成为当今web开发的固定模式了,为此也形成了众多的toolkit,譬如ror,django,各种js图形库更是玲琅满目,从非常大程度上也加速了开发过程.但传统web应用也非常自然地有一些诟病,有些特殊效果,c端能够轻而易举地完毕,…

邓白氏编码查询_外贸人常用查询工具汇总

外贸工具类网站FOB价格计算器http://bbs.fobshanghai.com/fobprice.htmCIF价格计算器http://www.easiertrade.com/public/cif.html?_1487894720000海关原产地证真伪查询https://dwz.cn/f3O8YGK6出口退税查询https://dwz.cn/kGWsBclu国家已正式于2018年11月1日起调整产品的出口…

winscp

简介&#xff1a;是linux的一个连接工具 1.winscp的下载&#xff1a;就会自动下载的了 2.安装配置&#xff1a; https://jingyan.baidu.com/article/6525d4b15bae6fac7d2e94a0.html 3.生成密钥&#xff1a; https://jingyan.baidu.com/article/ed2a5d1f377ccb09f6be178b.html 4…

gitlab-ee使用mysql_在 GitLab 我们是如何扩展数据库的

很长时间以来 GitLab.com 使用了一个单个的 PostgreSQL 数据库服务器和一个用于灾难恢复的单个复制。在 GitLab.com 最初的几年&#xff0c;它工作的还是很好的&#xff0c;但是随着时间的推移&#xff0c;我们看到这种设置的很多问题&#xff0c;例如&#xff0c;数据库长久处…

哈希表数据结构_Java数据结构哈希表如何避免冲突

前言一、哈希表是what&#xff1f;这是百度上给出的回答&#xff1a;简而言之&#xff0c;为什么要有这种数据结构呢&#xff1f;因为我们想不经过任何比较&#xff0c;一次从表中得到想要搜索的元素。所以就构造出来了哈希表&#xff0c;通过某种函数(哈希函数)使元素的存储位…

10 3 java_10.3 UiPath如何调用Java

调用Java方法(Invoke Java Method)的介绍从Java Scope中的.jar加载的方法中调用指定的Java方法。并结果存储在变量中二、Invoke Java Method 在UiPath中的使用打开设计器, 在设计库中新建一个Sequence&#xff0c;为序列命名及设置Sequence存放的路径, 在Activities中搜索Java …

台达伺服电机选型手册_机械加工工艺师手册_打包下载

如何【设为星标★】&#xff0c;优先推送资料信息&#xff1f;Ta们都在看咱们&#xff1a;机械大佬群注意及时保存和下载&#xff0c;资料若失效请拉到本页底部留言&#xff0c;我们将不定时补发&#xff01;免责声明&#xff1a;该资料系网络转载&#xff0c;版权归原作者所有…

团队作业7——Beta版本冲刺计划及安排

需要改进的工具流程&#xff08;如版本控制、测试工具等&#xff09; 首先把之前项目的BUG进行修复 然后完成如下的功能 冲刺的时间计划安排 &#xff08;冲刺时间为期七天&#xff0c;安排在2017.12.4——2017.12.10之间&#xff09; 组员任务陈福鹏实现博客.多语言、倒计…

开发黑名单功能demo_中台实践:通用化黑名单平台

业务中台的价值主要体现在对通用化业务能力的沉淀、整合&#xff0c;通过对可复用业务流程和业务功能的设计&#xff0c;向不同业务方提供标准化且可扩展的服务能力。本文来聊一聊笔者工作过程中设计的通用化黑名单平台&#xff0c;通过将用户管控能力的下沉&#xff0c;为各业…

Java旅游动吧项目讲解_springboot动吧项目

架构分析页面流程业务分析&#xff1a;客户端向服务端发送一个请求&#xff0c;发向了Tomcat&#xff0c;如果Tomcat只有一个线程是不可能处理多个请求的&#xff0c;所以就需要一个多个线程的池资源&#xff0c;然后线程用I/O读取请求中的数据&#xff0c;然后服务器从http协议…

java并发-内存模型与volatile

JMM的关键技术点都是围绕着多线程的原子性、可见性和有序性来建立的。因此&#xff0c;我们首先必须了解这些概念 1&#xff0c;原子性 原子性是指一个操作是不可中断的。即使是在多个线程一起执行的时候&#xff0c;一个操作一旦开始&#xff0c;就不会被其他线程干扰&#xf…