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,一经查实,立即删除!

相关文章

【GoWeb开发实战】Cookie

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

Spring中WebApplicationInitializer的理解

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

网络摄像头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 …

Elastic-job使用及原理

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

BeanShell自动装箱拆箱

“装箱”和“拆箱”是用来描述自动包装一个原始类型到一个包装类以及在必要时解开包装回到原始类型的术语。装箱是 Java 的特性&#xff08;SDK 1.5&#xff09;之一&#xff0c;且 BeanShell 已支持多年。 BeanShell 支持原始类型的装箱和拆箱。比如&#xff1a; int i5; Inte…

安装Docker step by step

1. 系统要求 centos7以上 使用cat /etc/redhat-release查看系统版本&#xff0c;我的Centos 7.6 centos-extra 仓库 enable&#xff0c;默认是打开的 2.安装docker docer安装分为联网安装和离线安装两种安装 方式&#xff0c; 第一种 在有外网环境下安装docker,一般使用yum安…

微信小程序 php配置,微信小程序的配置

我们使用app.json文件来对微信小程序进行全局配置&#xff0c;决定页面文件的路径、窗口表现、设置网络超时时间、设置多 tab 等。以下是一个包含了所有配置选项的简单配置app.json{"pages": ["pages/index/index","pages/logs/index"],"wi…

PWA - service worker - Workbox(未完)

Get Started&#xff08;开始&#xff09; 只有get请求才能cache缓存吗&#xff1f;Create and Register a Service Worker File&#xff08;创建和注册 Service Worker&#xff09; Before we can use Workbox, we need to create a service worker file and register it to o…

System类入门学习

第三阶段 JAVA常见对象的学习 System类 System类包含一些有用的字段和方法&#xff0c;他不能被实例化 //用于垃圾回收 public static void gc()//终止正在运行的java虚拟机。参数用作状态码&#xff0c;根据惯例&#xff0c;非0表示异常终止 public static void exit(int stat…

gulpfile php,Laravel利用gulp如何构建前端资源详解

什么是gulp&#xff1f;gulp是新一代的前端项目构建工具&#xff0c;你可以使用gulp及其插件对你的项目代码(less,sass)进行编译&#xff0c;还可以压缩你的js和css代码&#xff0c;甚至压缩你的图片&#xff0c;gulp仅有少量的API&#xff0c;所以非常容易学习。 gulp 使用 st…

8-python自动化-day08-进程、线程、协程篇

本节内容 主机管理之paramiko模块学习 进程、与线程区别python GIL全局解释器锁线程语法join线程锁之Lock\Rlock\信号量将线程变为守护进程Event事件 queue队列生产者消费者模型Queue队列开发一个线程池进程语法进程间通讯进程池 转载&#xff1a;  http://www.cnblogs.co…

select查询语句执行顺序

查询中用到的关键词主要包含六个&#xff0c;并且他们的顺序依次为 select--from--where--group by--having--order by 其中select和from是必须的&#xff0c;其他关键词是可选的&#xff0c;这六个关键词的执行顺序 与sql语句的书写顺序并不是一样的&#xff0c;而是按照下面的…

Python的Virtualenv(虚拟环境)的使用(Windows篇)2

Python的Virtualenv(虚拟环境)的使用&#xff08;Windows篇&#xff09; 2018年04月13日 11:35:01 D_FallMoon 阅读数 771 版权声明&#xff1a;版权所有 装载请注明 …

创建邮箱过程中的问题及解决办法

转自白手起家博客 http://bbs.chinaunix.net/forum.php?modviewthread&tid770141 说明一下&#xff1a;Q代表安装过程中遇到的问题&#xff0c;或者是日志中出现的现象。A&#xff1a;代表解决方法。 Q&#xff1a; Jan 13 11:26:29 mail authdaemond: failed to connect …

跟我一起屏蔽百度搜索页面右侧的内容

苦恼百度搜索热点等冗杂信息很久了&#xff0c;然后今天下定决心解决这个问题了。 第一步&#xff1a;搜索&#xff0c;并安装插件Adblock Plus 第二步&#xff1a;使用拦截器 1.打开拦截器 2.具体使用 点击这一块 添加 转载于:https://www.cnblogs.com/smart-girl/p/11058774.…

鼠标拖拽吸附效果

JavaScript鼠标拖动自动吸附实例 学了几天的JavaScript&#xff0c;自己动手做了一个简单的鼠标拖动的实例&#xff0c;拖动过程中科自动检测与目标容器的距离&#xff0c;在一定的距离范围内可以自动将被拖动的元素加入到目标容器中&#xff0c;希望对开始学习javascript的童鞋…

php修改mysql数据库中的表格,如何修改mysql数据库表?

修改mysql数据库表的方法&#xff1a;使用“ALTER TABLE”语句&#xff0c;可以改变原有表的结构&#xff0c;例如增加字段或删减字段、修改原有字段数据类型、重新命名字段或表、修改表字符集等&#xff1b;语法“ALTER TABLE [修改选项]”。修改数据表的前提是数据库中已经存…

微软最新GDI漏洞MS08-052安全解决方案

微软最新GDI漏洞MS08-052安全解决方案 Simeon微软于九月九日凌晨爆出有史以来最大的安全漏洞MS08-052&#xff0c;通过该漏洞&#xff0c;攻击者可以将木马藏于图片中&#xff0c;网民无论是通过浏览器浏览、还是用各种看图软件打开、或者在即时聊天窗口、电子邮件、Office文档…

DEM轨迹后处理

方法一&#xff1a;直接在paraview中显示 首先在输出颗粒信息的时候保存global ID&#xff1a;然后在paraview中导入vtp数据&#xff08;不要导入pvd&#xff09;&#xff0c;并使用Temporal Particle To Pathlines这个filter&#xff08;可以直接ctrlspace调出搜索框搜索&…

LVS的四种模式的实现

LVS 是四层负载均衡&#xff0c;也就是说建立在 OSI 模型的第四层——传输层之上&#xff0c;传输层上有我们熟悉的 TCP/UDP&#xff0c;LVS 支持 TCP/UDP 的负载均衡。LVS 的转发主要通过修改 IP 地址&#xff08;NAT 模式&#xff0c;分为源地址修改 SNAT 和目标地址修改 DNA…