ReentrantLock获取锁释放锁的流程示例分析
作者:Alan_YYL 发布时间:2021-08-05 20:51:10
目的
了解ReentrantLock获取锁、释放锁的流程
代码
package com.company.aqs;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* ReentrantLock使用案例——使用ReentrantLock加锁
* @Author: Alan
* @Date: 2022/11/20 01:38
*/
public class ReentrantLockDemo {
private static int sum=0;
private static Lock lock=new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 3; i++) {
new Thread(()->{
// 获取锁
lock.lock();
try {
for (int j = 0; j < 1000; j++) {
sum++;
}
}finally {
// 在finally代码块中释放锁
lock.unlock();
}
}).start();
}
// 保证所有线程执行完毕
Thread.sleep(1000);
System.out.println(sum);
}
}
这是一个使用ReentrantLock实现多线程求和的案例。代码逻辑比较简单,外层循环开启了3个线程,然后每个线程内多sum累加1000,最后输出结果sum=1000。
获取锁流程
整个过程概括起来就做了两件事儿
获取锁成功,执行当前线程内的其他事情;
获取锁失败,当前线程加入同步队列,同时阻塞当前线程。
当第一个线程(thead0)进来的时候,通过CAS去修改state属性为1,如果成功,通过setExclusiveOwnerThread()方法设置exclusiveOwnerThread为当前线程
此时,第二个线程(thead1)进来,再去通过CAS修改state属性为1时,便会失败,此时进入acquire()方法。最终会走到如下方法,首先通过tryAcquire()方法再次尝试去获取锁。
tryAcquire()方法内部还是会通过CAS去获取锁。此时锁资源还被第二线程持有,因此会返回false。现在接着看acquire()方法中的if判断。
此时,会进行acquireQueued(addWaiter(Node.EXCLUSIVE), arg))判断。这里需要执行两个方法addWaiter()和acquireQueued()。首先看addWaiter()方法。这个方法,我们需要关注以下四点。
首先会先构造一个node节点(节点内部细节,可以看其构造方法)。
如果tail(同步队列尾节点指针)不为空,其实也就是同步队列不为空,那么就把第1步构建的节点通过尾插法加入队列中,然后返回
如果同步队列为空了,那么执行enq()方法,这个方法为我们做了两件事儿。
如果同步队列为空,那么初始化队列
队列初始化完成后,将node节点入队。
通过addWaiter()方法和enq()方法,我们也可以看出来,AQS中的同步队列是通过双向链表来实现的,节点入队和出队,需要修改两个指针才行(prev和next)。
addWaiter()方法执行执行完毕后,我们通过下面这张图大致看下此时同步队列中的节点指向情况。此处,不太理解可以再回头看看enq()方法的执行流程。
addWaiter()方法执行执行完毕后,会返回入队后的新节点。然后开始执行acquireQueued()方法。这个方法做了五件事儿。
获取当前节点的前驱节点p
如果p是头节点,那么再次尝试去获取锁,获取锁成功,就可以跳出循环
获取锁失败,通过shouldParkAfterFailedAcquire()去修改waitSatus为-1(为什么修改为-1,这里可以从AQS的源码中找到原因,后续获取锁的流程也会遵从这个逻辑)
4. parkAndCheckInterrupt()方法会阻塞当前线程,同时,能够返回当前线程的中断状态(Thread.interrupted()会清除中断标记位)。
经过上述的一通操作,我们可以知道,线程1没有获取到锁,被加入到了同步队列,并且还对其进行了阻塞。概括下就是四个字“入队”、“阻塞”。此时我们的同步队列也变成如下所示。和上述执行完addWaiter()方法相比,只是线程1节点的前驱节点,waitStaus被设置成了-1。
释放锁流程
整个过程(只考虑释放锁成功)概括起来做了三件事儿
释放锁资源;
唤醒同步队列中头节点的后一个节点对应的线程
步骤2被唤醒的该线程尝试竞争锁,竞争锁成功,那么更新同步队列(即头节点出队)。
当第一个线程(thread0)执行完自己的业务流程后,就会释放锁。此时,我们来看看释放锁的流程又是什么样子的。调用unlock()方法可以对锁进行释放,需要注意的时,为了避免死锁,需要将该方法的调用放在fiaally代码块中。
当thread0去释放锁时,会调用release()方法,该方法主要做了两件事儿。
调用tryRelease()方法释放锁
其方法内部,会将state设置为0,同时通过setExclusiveOwnerThread()方法设置exclusiveOwnerThread为当null,代表此时锁资源被释放,没有任何线程持有锁资源
如果第一步返回true,即释放锁成功,那么开始第二步。第二步首先获取同步队列的头节点,查看其waitStatus属性。这里我们把刚才获取锁过后,同步队列中的节点情况再放一下。此时,我们的head节点的waitStatus为-1,因此会进入unparkSuccessor()方法
unparkSuccessor()方法,主要做了以下三件事儿。注意第三步,根据我们当前同步队列的情况来看,LockSupport.unpark()方法会唤醒线程1。
当执行完unparkSuccessor()方法中的LockSupport.unpark()方法后,线程1(thread1)就会被唤醒了。线程1被唤醒过后,我们又来看看线程1的执行情况。此时线程1,会继续执行acquireQueued()方法中的for循环(注意:这是一个死循环哦)。执行顺序和获取锁时是一致的,和获取锁有所不同的时,此时执行第2步获取锁是会成功的(因为thread0已经释放锁了)。
获取锁成功后呢,会让当前同步队列中的头节点出队,此时,同步队列中的节点情况如下所示。
此时,释放锁的逻辑就执行完成了。归纳起来其实也很简单,首先释放锁资源,然后再唤醒同步队列中头节点的后一个节点对应的线程,最后,更新同步队列(出队)。
来源:https://juejin.cn/post/7168914713814761503


猜你喜欢
- 1. 起源KV项目下载底层重构升级决定采用独立进程进行Media下载处理,以能做到模块复用之目的,因此涉及到了独立进程间的数据传递问题。目前
- 本文实例讲述了C#实现在图像中绘制文字图形的方法。分享给大家供大家参考。具体实现方法如下:using System;using System
- 1.创建动画控制器,双击打开动画控制器,创建 状态并添加动画片段,并且状态与状态之间进行连线,往返的都要有,在Animator的左上角–Pa
- 实现消息队列的两种方式Apache ActiveMQ官方实例发送消息直接在Apache官网http://activemq.apache.or
- 目录前言Binder的使用模糊进程间调用Binder原理ioctlbinder初始化总结前言Binder是安卓中实现IPC(进程间通信的)常
- 实现流程初始化一定数量的任务处理线程和缓存线程池,用户每次调用接口,开启一个线程处理。假设初始化5个处理器,代码执行 BlockingQue
- 一、简介Join方法主要是用来阻塞调用线程,直到某个线程终止或经过了指定时间为止。官方的解释比较乏味,通俗的说就是创建一个子线程,给它加了这
- Java原生API并不支持为应用程序设置全局热键。要实现全局热键,需要用JNI方式实现,这就涉及到编写C/C++代码,这对于大多数不熟悉C/
- Android Notification使用方法总结一. 基本使用1.构造notification NotificationCompat.B
- 本文实例讲述了C#简单获取全屏中鼠标焦点位置坐标的方法。分享给大家供大家参考,具体如下:using System;using System.
- 今天我们要开始来讲讲Java中的数组,包括一维数组和二维数组的静态初始化和动态初始化数组概述:数组可以看成是多个相同类型数据的组合,对这些数
- 本文实例为大家分享了Android实现文字下方加横线的具体代码,供大家参考,具体内容如下public class WhiteTextview
- service是业务层 action层即作为控制器DAO (Data Access Object) 数据访问1.JAVA中Action层,
- Java选择的泛型类型叫做类型擦除式泛型。什么是类型擦除式泛型呢?就是Java语言中的泛型只存在于程序源码之中,在编译后的字节码文件里,则全
- 前言之前在SpringBoot项目中一直使用的是SpringFox提供的Swagger库,上了下官网发现已经有接近两年没出新版本了!前几天升
- 对象是使用new创建的,但是并没有与之相对应的delete操作来回收对象占用的内存。当我们完成对某个对象的使用时,只需停止对该对象的引用:将
- JVM(Java虚拟机)是一个抽象的计算模型。就如同一台真实的机器,它有自己的指令集和执行引擎,可以在运行时操控内存区域。目的是为构建在其上
- 目标效果: 点击动画按钮之后每张牌各自旋转 散开到屏幕上半部分的任意位置之后回到初始位置 比较像LOL男刀的技能动画 : )1: 创建卡牌对
- 由于最近想要阅读下JDK1.8 中HashMap的具体实现,但是由于HashMap的实现中用到了红黑树,所以我觉得有必要先复习下红黑树的相关
- 1.SpringCache的概念首先我们知道jpa,jdbc这些东西都是一些规范,比如jdbc,要要连接到数据库,都是需要用到数据库连接,预