Java多线程篇(6)——AQS之ReentrantLock
文章目录
- 1、管程
- 2、AQS
- 3、ReentrantLock
- 3.1、lock/unlock
- 3.1.1、lock
- 3.1.2、unlock
- 3.2、一些思考
1、管程
什么是管程?
管理协调多个线程对共享资源的访问,是一种高级的同步机制。
有哪些管程模型?
hansen:唤醒其他线程的代码必须在当前线程的最后执行,以确保其他线程被唤醒时,当前线程已经执行完。
hoare:唤醒其他线程的代码可以在任意位置,且唤醒其他线程后,当前线程立即阻塞,当被唤醒线程执行完后,再继续执行当前线程。
mesa:唤醒其他线程的代码可以在任意位置,且被唤醒线程不会立即执行,而是加入一个队列,当当前线程执行完后,再从队列中获取并执行其他线程。(需要注意的是,由于存在时间差,所以真正执行的时候有可能已经不满足条件)
其中最常用的管程模型就是mesa,在java中实现的也是该模型。如下图是AQS的实现:

其实管程模型在java里的实现不止有AQS,synchronized 在jvm的底层实现像什么 cxq,entryList,waitSet也是mesa管程模型中的概念,同理object.wait()/object.notify()也是管程模型的实现。
2、AQS
所以具体什么是aqs?
aqs就是java代码对管程模型的一个抽象实现。把volatile state字段定义成共享资源,并且实现了同步等待队列和条件等待队列的入队/出队以及线程的阻塞/唤醒等公共操作,至于具体共享资源的获取/释放则交由各自实现类,不同的实现类可以定义不同的共享资源获取方式,由此可以实现公平锁/非公平锁,重入锁/不可重入锁,独占锁/共享锁等以满足不同的场景。
简单的看个印象

共享资源:

同步等待队列:

条件等待队列:

获取共享资源:

释放共享资源:

阻塞:

唤醒:

3、ReentrantLock
AQS最经典的实现莫过于 ReentrantLock。接下来看看是 ReentrantLock 如何通过AQS实现 lock/unlock 的。
3.1、lock/unlock
3.1.1、lock
AbstractQueuedSynchronizer.acquire
获取共享资源模板(aqs实现)

ReentrantLock .tryAcquire
共享资源获取逻辑(即ReentrantLock 自己实现的获锁方法)
在ReentrantLock中实现了公平/非公平两种获锁方式(默认为非公平)
以为非公平为例:
@ReservedStackAccessfinal boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {//cas修改共享资源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;}
AbstractQueuedSynchronizer.addWaiter
获锁失败后入队同步队列(aqs实现)
private Node addWaiter(Node mode) {Node node = new Node(mode);for (;;) {Node oldTail = tail;if (oldTail != null) {//cas加入队列node.setPrevRelaxed(oldTail);if (compareAndSetTail(oldTail, node)) {oldTail.next = node;return node;}} else {//初始化队列initializeSyncQueue();}}}
AbstractQueuedSynchronizer.acquireQueued
入队后阻塞之前,根据前节点的状态进行一定次数的自旋获锁(aqs实现)
final boolean acquireQueued(final Node node, int arg) {boolean interrupted = false;try {//这个循环保证一定会获取到锁for (;;) {final Node p = node.predecessor();//如果前一个节点是头结点,再次尝试获锁if (p == head && tryAcquire(arg)) {//如果获锁成功就出队头结点setHead(node);p.next = null;return interrupted;}//如果获锁失败或者前节点不是head的节点就根据前节点的状态来看是否需要阻塞//需要阻塞就调用 LockSupport.park() 阻塞线程if (shouldParkAfterFailedAcquire(p, node))interrupted |= parkAndCheckInterrupt();}} catch (Throwable t) {cancelAcquire(node);if (interrupted)selfInterrupt();throw t;}}private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {int ws = pred.waitStatus;//如果前驱节点状态为 SIGNAL 直接返回 true 阻塞线程if (ws == Node.SIGNAL)return true;//如果前驱节点状态大于0,说明线程已被返回,剔除无用节点前驱节点if (ws > 0) {do {node.prev = pred = pred.prev;} while (pred.waitStatus > 0);pred.next = node;} //cas替换前驱节点状态为 SIGNALelse {pred.compareAndSetWaitStatus(ws, Node.SIGNAL);}return false;}private final boolean parkAndCheckInterrupt() {LockSupport.park(this);return Thread.interrupted();}
也就是说,获锁失败进入同步等待队列进行阻塞,而在实际阻塞之前会自旋再次尝试获锁。
实际上在自旋途中只有前节点是head的节点才会尝试获锁
如果前节点的 waitStatus 为 signal 则停止自旋
为避免无限自旋,自旋的同时会尝试修改前节点的状态为 signal
3.1.2、unlock
AbstractQueuedSynchronizer.release
释放共享资源模板(aqs实现)

h.waitStatus == 0 说明后面没有等待唤醒的节点
ReentrantLock .tryRelease
共享资源释放逻辑(即ReentrantLock 自己实现的释放锁方法)
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);}//直接修改state值(不需要cas或者什么操作,因为释放锁不可能有并发)setState(c);return free;}
AbstractQueuedSynchronizer.unparkSuccessor
唤醒节点(aqs实现)
private void unparkSuccessor(Node node) {//清除node节点的waitStatus,重置为0int ws = node.waitStatus;if (ws < 0)node.compareAndSetWaitStatus(ws, 0);//如果node节点的后继节点被取消或者为空,就从尾部向前遍历找到实际的未取消后继节点。Node s = node.next;if (s == null || s.waitStatus > 0) {s = null;for (Node p = tail; p != node && p != null; p = p.prev)if (p.waitStatus <= 0)s = p;}//LockSupport.unparkif (s != null)LockSupport.unpark(s.thread);}
3.2、一些思考
1、公平锁和非公锁的区别在哪?

2、如何实现可中断的?

3、如何实现可超时的?

4、一开始没有线程获取锁,第一获取锁的线程进来直接获锁成功返回,没有入队操作,如何唤醒后继的线程?
答:虽然获锁线程没有入队,但是如果后续有等待线程需要用到队列的话还是会new一个node用于表示之前获锁线程的(相当于之前的获锁线程入队了)。

5、被唤醒的节点如何出队?

相关文章:
Java多线程篇(6)——AQS之ReentrantLock
文章目录 1、管程2、AQS3、ReentrantLock3.1、lock/unlock3.1.1、lock3.1.2、unlock 3.2、一些思考 1、管程 什么是管程? 管理协调多个线程对共享资源的访问,是一种高级的同步机制。 有哪些管程模型? hansen:唤醒其他线程的代码…...
【计算机网络】IP协议第二讲(Mac帧、IP地址、碰撞检测、ARP协议介绍)
IP协议第二讲 1.IP和Mac帧2.碰撞检测2.1介绍2.2如何减少碰撞发生2.3MTU2.4一些补充 3.ARP协议3.1协议介绍3.2报文格式分析 1.IP和Mac帧 IP(Internet Protocol)和MAC(Media Access Control)帧是计算机网络中两个不同层次的概念&am…...
TouchGFX界面开发 | 按钮控件应用示例
按钮控件应用示例 按钮是最常见的部件之一,有了按钮就可以点击,从而响应事件,达到人机交互的目的。TouchGFX Designer内置了七种按钮部件: 下压按钮:能够在被释放时发送回调,按下和释放状态都关联了图像标…...
BSVD论文理解:Real-time Streaming Video Denoising with Bidirectional Buffers
BSVD是来自香港科技大学的一篇比较新的视频去噪论文,经实践,去噪效果不错,在这里分享一下对这篇论文的理解。 论文地址:https://arxiv.org/abs/2207.06937 代码地址:GitHub - ChenyangQiQi/BSVD: [ACM MM 2022] Real…...
共同见证丨酷雷曼武汉运营中心成立2周年
酷雷曼武汉运营中心2周年 全国合作商齐贺武汉公司2周年庆 2021年 作为酷雷曼辐射全国版图的又一重要据点 酷雷曼武汉运营中心 在“中国光谷”正式成立 沉浸式参观酷雷曼武汉公司 2年时间 尽管历经诸多客观因素的挑战 但后浪扬帆,依然交出了不斐的成绩 解决…...
一种单键开关机电路图
我们设计产品时,通常需要设计单键开关机功能。 单键开关机,通常需要单片机的两个IO完成,一个IO用于保持开机状态。另外,一个IO用于判定关机状态。 下面就是一种单键开关机电路原理图: 此单键开关电路已经在S2W-M02、S2…...
设计模式2、抽象工厂模式 Abstract Factory
解释说明:提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类。 简言之,一个工厂可以提供创建多种相关产品的接口,而无需像工厂方法一样,为每一个产品都提供一个具体工厂 抽象工厂(Abstra…...
C++ 32盏灯,利用进制和 与 或 进行设计
一共32盏灯,设计一个灯光控制系统,其中 台球部8盏灯 桌游区8盏灯 酒吧区8盏灯 休息区8盏灯 满足以下功能 1、能够独立控制每一盏灯 2、能够一次性打开或关闭一个区域的全部灯光 3、能够获取各个区域的灯光打开关闭情况 4、能够一次性关闭打开的灯&#x…...
Ffmpeg-(1)-安装:ubuntu系统安装Ffmpeg应用
1、下载源码压缩包 https://ffmpeg.org/download.html 点击Download Source Code下载即可 解压: tar -xvjf ffmpeg-snapshot.tar.bz2 得到:ffmpeg目录 cd ffmpeg 或者:直接下 wget http://www.ffmpeg.org/releases/ffmpeg-5.1.tar.gztar -zx…...
系统集成|第十一章(笔记)
目录 第十一章 项目人力资源管理11.1 项目人力资源管理的定义及有关概念11.2 主要过程11.2.1 编制项目人力资源管理计划11.2.2 组建项目团队11.2.3 建设项目团队11.2.4 管理项目团队 11.3 现代激励理论11.4 项目经理所需具备的影响力11.5 常见问题 上篇:第十章、质量…...
二叉树题目:二叉树剪枝
文章目录 题目标题和出处难度题目描述要求示例数据范围 解法思路和算法代码复杂度分析 题目 标题和出处 标题:二叉树剪枝 出处:814. 二叉树剪枝 难度 4 级 题目描述 要求 给定二叉树的根结点 root \texttt{root} root,返回移除了所有…...
JAVA中使用CompletableFuture进行异步编程
JAVA中使用CompletableFuture进行异步编程 1、什么是CompletableFuture CompletableFuture 是 JDK8 提供的 Future 增强类,CompletableFuture 异步任务执行线程池,默认是把异步任 务都放在 ForkJoinPool 中执行。 在这种方式中,主线程不会…...
uniapp:配置动态接口域名,根据图片访问速度,选择最快的接口
common.js // 动态测速选择的域名 // h5直接返回默认第一个域名 // vue文件用到域名的话用this.$baseURL let domains [{uri:192.168.31.215:9523, speed:0},{uri:api.ceshi.org, speed:0}, ]export const protocol {api: http://,//本地// api: https://api.,//正式h5Url: h…...
Lambda表达式常见用法(提高效率神器)
Java8中一个非常重要的特性就是Lambda表达式,我们可以把它看成是一种闭包,它允许把函数当做参数来使用,是面向函数式编程的思想,一定程度上可以使代码看起来更加简洁。 其实以上都不重要,重要的是能够提高我的开发效率…...
2023旷视自驾感知算法暑期实习一面
来源:投稿 作者:LSC 编辑:学姐 1. 问下项目,问下我的情况 2. 是否了解最新的BEV算法,讲一下 3. 是否了解三维重建 4. 考察相机坐标系的转换 5. 手撕代码,翻车了,不考leetcode,考…...
Python3 如何实现 websocket 服务?
Python 实现 websocket 服务很简单,有很多的三方包可以用,我从网上大概找到三种常用的包:websocket、websockets、Flask-Sockets。 但这些包很多都“年久失修”, 比如 websocket 在 2010 年就不维护了。 而 Flask-Sockets 也在 2…...
SQLAlchemy常用数据类型
目录 SQLAlchemy常用数据类型 代码演示 代码分析 SQLAlchemy常用数据类型 SQLAlchemy 是一个Python的SQL工具库和对象关系映射(ORM)工具,它提供了一种在Python中操作数据库的高效方式。下面是SQLAlchemy中常用的一些数据类型: Integer:整形&…...
Vue路由与nodejs下载安装及环境变量的配置
目录 前言 一、Vue路由 1.路由简介 是什么 作用 应用场景 2.SPA简介 SPA是什么 SPA的优点 注意事项 3.路由实现思路 1.引入路由的js依赖 2.定义组件 3.定义组件与路径的对应关系 4.通过路由关系获取路由对象router 5.将路由对象挂载到实例中 6.触发路由事…...
HarmonyOS之 应用程序页面UIAbility
一 UIAbility介绍: 1.1 UIAbility是一种包含用户界面的应用组件,主要用于和用户进行交互 1.2 UIAbility也是系统调度的单元,为应用提供窗口在其中绘制界面 二 UIAbility跳转和传参 2.1 页面间的导航可以通过页面路由router模块来实现。页…...
数据集笔记: Porto
数据来源:Taxi Trajectory Data_数据集-阿里云天池 (aliyun.com) 1 数据介绍 葡萄牙波尔图市运行的所有442辆出租车的全年轨迹(从2013年7月1日至2014年6月30日) 2 读取数据 import pandas as pdtrapd.read_csv(C:/Users/16000/Download…...
家政维修平台实战20:权限设计
目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系,主要是分成几个表,用户表我们是记录用户的基础信息,包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题,不同的角色…...
反射获取方法和属性
Java反射获取方法 在Java中,反射(Reflection)是一种强大的机制,允许程序在运行时访问和操作类的内部属性和方法。通过反射,可以动态地创建对象、调用方法、改变属性值,这在很多Java框架中如Spring和Hiberna…...
04-初识css
一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...
【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分
一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计,提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合:各模块职责清晰,便于独立开发…...
站群服务器的应用场景都有哪些?
站群服务器主要是为了多个网站的托管和管理所设计的,可以通过集中管理和高效资源的分配,来支持多个独立的网站同时运行,让每一个网站都可以分配到独立的IP地址,避免出现IP关联的风险,用户还可以通过控制面板进行管理功…...
如何应对敏捷转型中的团队阻力
应对敏捷转型中的团队阻力需要明确沟通敏捷转型目的、提升团队参与感、提供充分的培训与支持、逐步推进敏捷实践、建立清晰的奖励和反馈机制。其中,明确沟通敏捷转型目的尤为关键,团队成员只有清晰理解转型背后的原因和利益,才能降低对变化的…...
【SpringBoot自动化部署】
SpringBoot自动化部署方法 使用Jenkins进行持续集成与部署 Jenkins是最常用的自动化部署工具之一,能够实现代码拉取、构建、测试和部署的全流程自动化。 配置Jenkins任务时,需要添加Git仓库地址和凭证,设置构建触发器(如GitHub…...
pycharm 设置环境出错
pycharm 设置环境出错 pycharm 新建项目,设置虚拟环境,出错 pycharm 出错 Cannot open Local Failed to start [powershell.exe, -NoExit, -ExecutionPolicy, Bypass, -File, C:\Program Files\JetBrains\PyCharm 2024.1.3\plugins\terminal\shell-int…...
云原生周刊:k0s 成为 CNCF 沙箱项目
开源项目推荐 HAMi HAMi(原名 k8s‑vGPU‑scheduler)是一款 CNCF Sandbox 级别的开源 K8s 中间件,通过虚拟化 GPU/NPU 等异构设备并支持内存、计算核心时间片隔离及共享调度,为容器提供统一接口,实现细粒度资源配额…...
【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...
