java重入锁,再探JAVA重入锁

5afe4de83188424822e688c5ea4397f1.png

之前的文章中简单的为大家介绍了重入锁JAVA并发之多线程基础(2)。这里面也是简单的为大家介绍了重入锁的几种性质,这里我们就去探索下里面是如何实现的。

我们知道在使用的时候,必须锁先有定义,然后我们再拿着当前的锁进行加锁操作,然后处理业务,最后是释放锁的操作(这里就拿里面非公平锁的实现来讲解)。

字节码操作

public class com.montos.lock.ReentrantLockDemo implements java.lang.Runnable {

public static java.util.concurrent.locks.ReentrantLock lock;

public static int k;

public com.montos.lock.ReentrantLockDemo();

Code:

0: aload_0

1: invokespecial #1 // Method java/lang/Object."":()V

4: return

public void run();

Code:

0: iconst_0

1: istore_1

2: iload_1

3: sipush 1000

6: if_icmpge 29 //int类型的值进行栈顶比较

9: getstatic #2 // Field lock:Ljava/util/concurrent/locks/ReentrantLock;

12: invokevirtual #3 // Method java/util/concurrent/locks/ReentrantLock.lock:()V

15: getstatic #4 // Field k:I

18: iconst_1

19: iadd

20: putstatic #4 // Field k:I

23: iinc 1, 1

26: goto 2

29: iconst_0

30: istore_1

31: iload_1

32: sipush 1000

35: if_icmpge 50

38: getstatic #2 // Field lock:Ljava/util/concurrent/locks/ReentrantLock;

41: invokevirtual #5 // Method java/util/concurrent/locks/ReentrantLock.unlock:()V

44: iinc 1, 1

47: goto 31

50: return

public static void main(java.lang.String[]) throws java.lang.InterruptedException;

Code:

0: new #6 // class com/montos/lock/ReentrantLockDemo

3: dup

4: invokespecial #7 // Method "":()V

7: astore_1

8: new #8 // class java/lang/Thread

11: dup

12: aload_1

13: invokespecial #9 // Method java/lang/Thread."":(Ljava/lang/Runnable;)V

16: astore_2

17: new #8 // class java/lang/Thread

20: dup

21: aload_1

22: invokespecial #9 // Method java/lang/Thread."":(Ljava/lang/Runnable;)V

25: astore_3

26: aload_2

27: invokevirtual #10 // Method java/lang/Thread.start:()V

30: aload_3

31: invokevirtual #10 // Method java/lang/Thread.start:()V

34: aload_2

35: invokevirtual #11 // Method java/lang/Thread.join:()V

38: aload_3

39: invokevirtual #11 // Method java/lang/Thread.join:()V

42: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream;

45: getstatic #4 // Field k:I

48: invokevirtual #13 // Method java/io/PrintStream.println:(I)V

51: return

static {};

Code:

0: new #14 // class java/util/concurrent/locks/ReentrantLock

3: dup

4: invokespecial #15 // Method java/util/concurrent/locks/ReentrantLock."":()V

7: putstatic #2 // Field lock:Ljava/util/concurrent/locks/ReentrantLock;

10: iconst_0

11: putstatic #4 // Field k:I

14: return

}

复制代码

这里面无非就是入栈,栈元素比较,出栈放入变量中这些操作,没有之前的synchronized里面的监视器相关指令限制,只是简单的一些栈操作。

加锁操作

final void lock(){

if (compareAndSetState(0, 1)) //将同步状态从0变成1 采用cas进行更新

setExclusiveOwnerThread(Thread.currentThread());//设置当前拥有独占访问权的线程。

else

acquire(1);//没有获取到锁,则进行尝试操作

}

复制代码

往下面的选择走:

public final void acquire(int arg){

//先进行再次尝试获取锁的操作,如果获取失败则将当前加入队列中,并设置中断标志。

if (!tryAcquire(arg) &&

acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

selfInterrupt();

}

复制代码

首先走尝试获取锁的操作(这里还是走非公平锁的):

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) // overflow

throw new Error("Maximum lock count exceeded");

setState(nextc);//设置新的状态

return true;

}

return false;

}

复制代码

接着往下走:

private Node addWaiter(Node mode){

//独占模式进行封装当前线程

Node node = new Node(Thread.currentThread(), mode);

Node pred = tail;

if (pred != null) {如果尾节点不为null,将当前的节点接入并返回

node.prev = pred;

if (compareAndSetTail(pred, node)) {

pred.next = node;

return node;

}

}

enq(node);

return node;

}

复制代码

继续往下走:

private Node enq(final Node node){

for (;;) {//

Node t = tail;

if (t == null) { // 初始化尾节点

if (compareAndSetHead(new Node()))

tail = head;

} else {

node.prev = t;

if (compareAndSetTail(t, node)) {//尾节点与当前的节点互换

t.next = node;

return t;//返回当前节点

}

}

}

}

复制代码

接着回去往下走:

final boolean acquireQueued(final Node node, int arg){

boolean failed = true;

try {

boolean interrupted = false;

for (;;) {

final Node p = node.predecessor();

if (p == head && tryAcquire(arg)) {//如果当前节点前一个节点是头节点,并尝试获锁成功

setHead(node);//设置当前的头结点

p.next = null; // 手动清除引用 帮助GC

failed = false;

return interrupted;

}

//检测获取锁失败的节点状态 以及暂时挂起并返回当前的中断标志

if (shouldParkAfterFailedAcquire(p, node) &&

parkAndCheckInterrupt())

interrupted = true;

}

} finally {

if (failed)

cancelAcquire(node);//取消正在进行的获取尝试。

}

}

复制代码

说真的,咱们直接看失败的情况,我们接着往下走:

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node){

//检查和更新无法获取的节点的状态。

int ws = pred.waitStatus;

if (ws == Node.SIGNAL)

//该节点已经设置了请求释放信号状态,所以可以进行安全挂起

return true;

if (ws > 0) {

do {//清除不需要执行的节点

node.prev = pred = pred.prev;

} while (pred.waitStatus > 0);

pred.next = node;

} else {

//waitstatus必须为0或传播。表明我们需要信号,但不要挂起。调用者重试以确保在挂起前无法获取。

compareAndSetWaitStatus(pred, ws, Node.SIGNAL);

}

return false;

}

复制代码

然后看向下一个方法:

private final boolean parkAndCheckInterrupt(){

LockSupport.park(this);//挂起当前线程

return Thread.interrupted();//返回中断标识

}

复制代码

上面的取消获取队列里面的节点就不看了..cancelAcquire(node),里面就是取消正在进行的获取尝试。同时将无需的节点移除。当上面的操作走完之后就设置当前线程中断标识。这里面主要流程是说如果加锁不成功之后,对于当前线程是怎么执行操作的,我们可以看到,里面的方法中大部分在获取不到锁之后,下一步操作中会再次尝试获取下,如果获取不到才会继续执行,获取到了我们就可以直接使用,这里也是多线程操作里面的魅力,每一个空隙中就可能会让当前线程进行获得锁的操作。

释放锁操作

释放锁的步骤就简单许多了:

public final boolean release(int arg){

if (tryRelease(arg)) {//尝试释放锁

Node h = head;

if (h != null && h.waitStatus != 0)

unparkSuccessor(h);//唤醒节点的后续节点

return true;

}

return false;

}

复制代码

咱们继续往下看:

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;//只有当前重入次数为0,才能返回true

setExclusiveOwnerThread(null);//当前独占线程设为NULL

}

setState(c);//重新设置同步状态

return free;

}

复制代码

然后往下走:

private void unparkSuccessor(Node node){

//当前状态为负数,则尝试清除当前的线程状态

int ws = node.waitStatus;

if (ws < 0)

compareAndSetWaitStatus(node, ws, 0);

//清除取消或无效的节点,从尾部向后移动以找到实际节点

Node s = node.next;

if (s == null || s.waitStatus > 0) {

s = null;

for (Node t = tail; t != null && t != node; t = t.prev)

if (t.waitStatus <= 0)

s = t;

}

if (s != null)

LockSupport.unpark(s.thread);//释放当前线程

}

复制代码从上面的顺序往下面来看,我们主要发现线程在拿锁阶段是有许多的操作的,要根据线程的状态再将线程从等待队列中移除。释放的时候就显得简洁了许多,我们只需要看到当前线程的状态-1,然后看看是否是重入的。

我们通过一个简单的重入锁代码可以看到,作者在用无锁的操作去获得锁,这个整体的步骤里面考虑的东西很多,每一个时刻,线程都有可能千变万化,我们需要了解的是我们每一个步骤都需要可能发生的情况。如果能够考虑到发生的情况,那么有些步骤就可以直接跳过,我们直接就可以获得最后的结果(这块在线程尝试获锁的阶段可以体现)。有小伙伴对于重入锁还有什么看法的可以在下面进行留言,我们可以相互学习,共同进步~

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

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

相关文章

azure服务器_如何使用Azure Functions和SendGrid构建无服务器报表服务器

azure服务器It’s 2018 and I just wrote a title that contains the words “Serverless server”. Life has no meaning.那是2018年&#xff0c;我刚刚写了一个标题&#xff0c;其中包含“无服务器服务器”一词。 生活没有意义。 Despite that utterly contradictory headli…

【GoWeb开发实战】Cookie

cookie Web开发中一个很重要的议题就是如何做好用户的整个浏览过程的控制&#xff0c;因为HTTP协议是无状态的&#xff0c;所以用户的每一次请求都是无状态的&#xff0c;我们不知道在整个Web操作过程中哪些连接与该用户有关&#xff0c;我们应该如何来解决这个问题呢&#xff…

PhotoKit 照片库的管理-获取图像

PHAsset部分属性解析 1、HDR 和全景照片 mediaSubtypes 属性验证资源库中的图像在捕捉时是否开启了 HDR&#xff0c;拍摄时是否使用了相机应用的全景模式。 2、收藏和隐藏资源 要验证一个资源是否被用户标记为收藏或被隐藏&#xff0c;只要检查 PHAsset 实例的 favorite 和 hid…

cmail服务器安装后无法登录的解决办法

安装cmailserver 5.4.6软件安装、注册都非常顺利&#xff0c;webmail页面也都正常打开&#xff0c;但是一点“登录”就提示错误&#xff1a; Microsoft VBScript 运行时错误 错误 800a01ad ActiveX 部件不能创建对象: CMailCOM.POP3.1 /mail/login.asp&#xff0c;行 42 点“…

matlab对人工智能,MATLAB与人工智能深度学习和机器学习.PDF

MATLAB与人工智能深度学习和机器学习MATLAB 与人工智能&#xff1a;深度学习有多远&#xff1f;© 2017 The MathWorks, Inc.1机器学习8机器学习无处不在▪ 图像识别 [TBD]▪ 语音识别▪ 股票预测▪ 医疗诊断▪ 数据分析▪ 机器人▪ 更多……9什么是机器学习&#xff1f;机…

leetcode1471. 数组中的 k 个最强值(排序)

给你一个整数数组 arr 和一个整数 k 。 设 m 为数组的中位数&#xff0c;只要满足下述两个前提之一&#xff0c;就可以判定 arr[i] 的值比 arr[j] 的值更强&#xff1a; |arr[i] - m| > |arr[j] - m| |arr[i] - m| |arr[j] - m|&#xff0c;且 arr[i] > arr[j] 请返回…

Spring中WebApplicationInitializer的理解

现在JavaConfig配置方式在逐步取代xml配置方式。而WebApplicationInitializer可以看做是Web.xml的替代&#xff0c;它是一个接口。通过实现WebApplicationInitializer&#xff0c;在其中可以添加servlet&#xff0c;listener等&#xff0c;在加载Web项目的时候会加载这个接口实…

使用fetch封装请求_关于如何使用Fetch API执行HTTP请求的实用ES6指南

使用fetch封装请求In this guide, I’ll show you how to use the Fetch API (ES6) to perform HTTP requests to an REST API with some practical examples you’ll most likely encounter.在本指南中&#xff0c;我将向您展示如何使用Fetch API(ES6 )来执行对REST API的 HTT…

hadoop集群中客户端修改、删除文件失败

这是因为hadoop集群在启动时自动进入安全模式 查看安全模式状态&#xff1a;hadoop fs –safemode get 进入安全模式状态&#xff1a;hadoop fs –safemode enter 退出安全模式状态&#xff1a;hadoop fs –safemode leave转载于:https://www.cnblogs.com/lishengnan/p/a123.ht…

OpenStack nova-network 支持多vlan技术实现片段代码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748

Rest API

什么是接口测试 接口测试又称 API 测试 Application Programming Interface 接口测试是测试系统组件间接口的一种测试。重点关注数据传递。 接口测试一般会用于多系统间交互开发&#xff0c;或者拥有多个子系统的应用系统开发的测试。 为什么要做接口测试 很多系统关联都是基于…

php循环checkbox,php循环删除checkbox | 学步园

一、首先要了解sql语句$SQLdelete from user where id in (1,2,4);表单大概是&#xff1a;form action methodpost input nameID_Dele[] typecheckbox idID_Dele[] value1input nameID_Dele[] typecheckbox idID_Dele[] value2input nameID_Dele[] type首先要了解sql语句$SQL&q…

leetcode1451. 重新排列句子中的单词(排序)

「句子」是一个用空格分隔单词的字符串。给你一个满足下述格式的句子 text : 句子的首字母大写 text 中的每个单词都用单个空格分隔。 请你重新排列 text 中的单词&#xff0c;使所有单词按其长度的升序排列。如果两个单词的长度相同&#xff0c;则保留其在原句子中的相对顺序…

Java+Oracle实现事务——JDBC事务

J2EE支持JDBC事务、JTA事务和容器事务事务&#xff0c;这里说一下怎样实现JDBC事务。 JDBC事务是由Connection对象所控制的&#xff0c;它提供了两种事务模式&#xff1a;自己主动提交和手动提交&#xff0c;默认是自己主动提交。 自己主动提交就是&#xff1a;在JDBC中。在一个…

开源项目贡献者_我如何从一名贡献者转变为一个开源项目维护者

开源项目贡献者by Dhanraj Acharya通过Dhanraj Acharya 我如何从一名贡献者转变为一个开源项目维护者 (How I went from being a contributor to an Open Source project maintainer) I was a lone software developer. When I was in college, I attended the KDE conference…

网络摄像头CVE

CVE-2018-9995 rtsp未授权访问 rtsp后缀整理&#xff1a; Axis&#xff08;安讯士&#xff09; rtsp:// 192.168.200.202/axis-media/media.amp?videocodech264&resolution1280x720 rtsp://IP地址/mpeg4/media.amp rtsp://IP地址/安迅士/AXIS-media/media.amp123D-Link …

Centos中不从skel目录里向其中复制任何文件错误的解决方法

[rootlocalhost www]# useradd -d /webserver/www/ ithovcom useradd&#xff1a;警告&#xff1a;此主目录已经存在。 不从 skel 目录里向其中复制任何文件。 [rootlocalhost www]# ls -a .&http://www.aliyun.com/zixun/aggregation/37954.html">nbsp; .. 发现没…

leetcode91. 解码方法

一条包含字母 A-Z 的消息通过以下方式进行了编码&#xff1a; ‘A’ -> 1 ‘B’ -> 2 … ‘Z’ -> 26 给定一个只包含数字的非空字符串&#xff0c;请计算解码方法的总数。 示例 1: 输入: “12” 输出: 2 解释: 它可以解码为 “AB”&#xff08;1 2&#xff09;或者…

php 系统平均负载,Linux_解析Linux系统的平均负载概念,一、什么是系统平均负载(Load a - phpStudy...

解析Linux系统的平均负载概念一、什么是系统平均负载(Load average)&#xff1f;在Linux系统中&#xff0c;uptime、w、top等命令都会有系统平均负载load average的输出&#xff0c;那么什么是系统平均负载呢&#xff1f;系统平均负载被定义为在特定时间间隔内运行队列中的平均…

Elastic-job使用及原理

一、原理 elastic-job有lite版和cloud版&#xff0c;最大的区别是有无调度中心&#xff0c;笔者采用的是lite版本&#xff0c;无中心化。 tips: 第一台服务器上线触发主服务器选举。主服务器一旦下线&#xff0c;则重新触发选举&#xff0c;选举过程中阻塞&#xff0c;只有主服…