ReentrantLock加锁分析

1、ReentrantLock中其实是有一个AQS的子类实例的成员变量sync;

2、实际是调用的Sync中的lock;Sync是AQS的子类;Sync有两个子类,公平与非公平;默认为非公平;如下是非公平加锁分析;

    public ReentrantLock() {sync = new NonfairSync();}

3、cas进行加锁:0->1,成功即加锁成功,并进行业务逻辑处理,然后解锁;

compareAndSetState(0, 1)

4、cas加锁:0->1 会有两种情况导致失败:

  1. 锁被其它线程占用;
  2. 已经被当前线程(自己)占用;
        final void lock() {if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());elseacquire(1);}

5、cas失败,则acquire(1);

    public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}

        如上可以避免一些情况下导致刚刚加入队列的线程无法被唤醒的情况。

6、进行tryAcquire(1);其中会再一次进行cas设置,进行锁重入逻辑判断并处理;

        final 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;}

        如上代码,我曾还怀疑是否存在 setState(nextc); 线程安全的问题?

        心想着在else if (current == getExclusiveOwnerThread()) 判断成立之后,锁被释放,并且被其它线程重新获取锁成功。然后再进行 int nextc = c + acquires; setState(nextc); 就会存在线程不安全问题。

        然后仔细琢磨,current == getExclusiveOwnerThread() 既然成立,说明持有当前锁的线程就是正在执行当前方法逻辑的线程,那么既然当前线程正在执行tryAcquire逻辑,又怎么可能在这个时候去释放锁,所以这里是线程安全的。

7、如果 !tryAcquire(arg) 再次cas没有成功并且不是进行多次重入加锁,那么就会进行将当前线程构建成Node并加入队列的操作;

8、addWaiter(Node.EXCLUSIVE),创建一个等待Node,并且这个Node是一个互斥独占的类型;

    static final Node SHARED = new Node();static final Node EXCLUSIVE = null;addWaiter(Node.EXCLUSIVE)Node node = new Node(Thread.currentThread(), mode);Node(Thread thread, Node mode) {// 这样就很方便的知道了当前队列是共享型阻塞队列,还是独占型阻塞队列this.nextWaiter = mode;this.thread = thread;}
    private Node addWaiter(Node mode) {Node node = new Node(Thread.currentThread(), mode);Node pred = tail;if (pred != null) {// 表示队列之前已经被初始化过node.prev = pred;// 如果入队cas没有成功则会进行enq(node)if (compareAndSetTail(pred, node)) {// 这里node入队列尾部,同样使用cas做了并发安全控制pred.next = node;return node;}}// 将节点插入队列,如有必要进行初始化enq(node);return node;}

9、将节点插入队列,如有必要进行初始化。enq(node); 如下操作会一直自旋直到把node插入队列为止

    private Node enq(final Node node) {for (;;) {// 这里就必须要把node搞进队列为止了,如果一次不行则两次......Node t = tail; // 所以tail必须为volatile修饰if (t == null) { // Must initialize// 队列初始化if (compareAndSetHead(new Node()))// 虚拟一个Node,设置为头(不包含线程)// 如果不成功则下次for循环tail = head;} else {// 队列已经存在nodenode.prev = t;if (compareAndSetTail(t, node)) { // 将新增的node设置为尾t.next = node;return t;}}}}

10、acquireQueued(node,1);这个方法中包含三种情况的逻辑;

  1. 当前线程的Node的pre就是head,并且再次尝试cas:0->1成功,那么就需要将当前线程的Node设置为head,并且将原先的head进行出队列;(如果当前线程想要去竞争锁,那么前提条件是当前线程的Node必须是head
  2. 当前线程的Node的pre不是head或者cas:0->1失败,那么就需要将当前线程进行park让其阻塞,等待被释放锁的线程唤醒;
  3. Node对应的线程被唤醒后,重新执行for循环判断,cas获取锁;
    final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;for (;;) {// 获取当前线程Node的前驱节点final Node p = node.predecessor();if (p == head && tryAcquire(arg)) {// 当前线程获取锁成功,及当前参数Node中的线程就是当前线程// 并且当前Node是此时队列中的第二个// 那么代表第一个Node已经使用完锁,需要将其进行从队列中移除// 当前线程的Node设置为队列的头setHead(node);p.next = null; // help GCfailed = false;return interrupted;}// 进行线程阻塞操作if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt()) // 阻塞当前线程,被唤醒后判断当前线程释放被中断interrupted = true; // 阻塞被唤醒后并且当前线程已经被中断}} finally {if (failed)cancelAcquire(node);}}private void setHead(Node node) {head = node;node.thread = null;node.prev = null;}

11、shouldParkAfterFailedAcquire(Node pred, Node node),当前线程Node的前驱节点的状态是SIGNAL(前驱节点有义务唤醒它的下一个)时,才会将当前线程进行park操作;

    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {int ws = pred.waitStatus;if (ws == Node.SIGNAL)/** This node has already set status asking a release* to signal it, so it can safely park.*/// 当前驱节点被赋予责任后(需要唤醒后续节点)return true;else {/** waitStatus must be 0 or PROPAGATE.  Indicate that we* need a signal, but don't park yet.  Caller will need to* retry to make sure it cannot acquire before parking.*/// 这里会将前驱阶段的状态设置为:需要唤醒后续节点compareAndSetWaitStatus(pred, ws, Node.SIGNAL);}

12、parkAndCheckInterrupt(),进行当前线程park操作;

    private final boolean parkAndCheckInterrupt() {// 这里会进行当前线程阻塞,直到被唤醒LockSupport.park(this);// 检查被唤醒的线程释放已经被中断return Thread.interrupted();}

13、会有两种情况线程会被唤醒继续执行 return Thread.interrupted(); 之后的逻辑

  1. 被park阻塞后的线程被唤醒,并且目标线程被中断,那么上面方法会返回true;
  2. 被park阻塞后的线程被唤醒,目标线程未被中断;

14、被唤醒的线程再次cas尝试获取锁,如果获取锁失败会继续进行park,循环往复,直到获取锁成功。

好文推荐:

AQS源码分析(四)[lock流程图总结与unlock源码分析]_哔哩哔哩_bilibili

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

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

相关文章

Visual Basic6.0零基础教学(4)—编码基础,数据类型与变量

编码基础,数据类型与变量 文章目录 编码基础,数据类型与变量前言一、VB中的编程基础二、VB的基本字符集和词汇集1、字符集2、词汇集 VB中的数据类型VB中的变量与常量一.变量和常量的命名规则二.变量声明1.用Dim语句显式声明变量三. 常量 运算符和表达式一. 运算符 1. 算术运算符…

获取Book里所有sheet的名字,且带上超链接

应用背景&#xff1a; 当一个excel有很多sheet的时候&#xff0c;来回切换sheet会比较复杂&#xff0c;所以我希望excel的第一页有目录&#xff0c;可以随着sheet的增加&#xff0c;减少&#xff0c;改名而随时可以去更新&#xff0c;还希望有超链接可以直接跳到该sheet。 可以…

06-验证浮点数输入

鉴于shell脚本的限制和本事&#xff0c;浮点数&#xff08;或“实数”&#xff09;的验证过程乍一看似乎让人望而生畏&#xff0c;不过考虑到浮点数只不过是由小数点分隔的两个整数&#xff0c;再配合能够在脚本中引用其他脚本的能力&#xff08;validint&#xff09;&#xff…

【爬取网易财经文章】

引言 在信息爆炸的时代&#xff0c;获取实时的财经资讯对于投资者和金融从业者来说至关重要。然而&#xff0c;手动浏览网页收集财经文章耗时费力&#xff0c;为了解决这一问题&#xff0c;本文将介绍如何使用Python编写一个爬虫程序来自动爬取网易财经下关于财经的文章 1. 爬…

前端基础 Vue -组件化基础

1.全局组件 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><script src&…

【QA】MySQL导出某数据库的所有数据为sql文件,包含建库命令、建表命令。

文章目录 前言Windows系统下 | mysqldump导出数据库数据Docker中导入初始化数据【补充】通过命令行&#xff0c;执行sql文件&#xff0c;将数据导入到数据库在MySQL外面执行在MySQL中执行 前言 我们在用docker部署mysql项目的时候&#xff0c;往往需要对数据库进行数据初始化。…

Java-SSM电影在线播放系统

Java-SSM电影在线播放系统 1.服务承诺&#xff1a; 包安装运行&#xff0c;如有需要欢迎联系&#xff08;VX:yuanchengruanjian&#xff09;。 2.项目所用框架: 前端:JSP、layui等 后端:SSM,即Spring、SpringMvc、Mybatis等。 3.项目功能点: 3-1.后端功能: - 所有后台管理展…

rk3588内核添加特殊分辨率

rk平台内核本身默认支持一些常规的分辨率,如1920x1080@30,1280x720@60,但是往往不能满足需求,如有的客户需要你添加1020x700@35的分辨率,这时候就要自己加上去了。 下图是LCD各个参数对应的位置: 显示mode 各个参数含义如下: hdisplay:有效显示区水平像素数量,对应A…

Redis入门到实战-第十弹

Redis实战热身Geospatial篇 完整命令参考官网 官网地址 声明: 由于操作系统, 版本更新等原因, 文章所列内容不一定100%复现, 还要以官方信息为准 https://redis.io/Redis概述 Redis是一个开源的&#xff08;采用BSD许可证&#xff09;&#xff0c;用作数据库、缓存、消息代…

[综述笔记]Flexible large-scale fMRI analysis: A survey

论文网址&#xff1a;Flexible large-scale fMRI analysis: A survey | IEEE Conference Publication | IEEE Xplore 英文是纯手打的&#xff01;论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误&#xff0c;若有发现欢迎评论指正&#xff0…

关于网格数据导出指定格式的测试(以Gmsh导出nas格式为例)

本文主要讲述Gmsh如何导出nas格式的网格数据&#xff0c;众所周知&#xff0c;Gmsh可以导出多种网格数据格式&#xff0c;比如大家熟悉的msh、stl、inp、cgns&#xff08;似乎不完善&#xff09;等等&#xff0c;但是gmsh不支持nas格式的导出&#xff0c;只支持nas格式的导入&a…

银行量子金融系统应用架构设计

量子金融&#xff08;即Financial-Quantum&#xff0c;简称Fin-Q&#xff09;&#xff0c;特指量子科技在金融行业中的应用。 目前&#xff0c;量子科技中以量子保密通信、量子随机数和量子计算发展进度较快&#xff0c;取得了诸多阶段性重大技术突破和商用成果&#xff0c;这…

Linux Ncurses库部分函数使用说明

目录 1. initscr&#xff08;&#xff09;函数 2. endwin&#xff08;&#xff09;函数 3. curs_set()函数 4.noecho()函数 5. keypad()函数 6. start_color()函数 7.init_pair()函数 8.getch()函数 9.move()函数 10.addch()函数 11. refresh()函数 12.inch()函数…

实战|使用 Node.js 和 htmx 构建全栈应用程序

在本教程中&#xff0c;我将演示如何使用 Node 作为后端和 htmx 作为前端来构建功能齐全的 CRUD 应用程序。这将演示 htmx 如何集成到全栈应用程序中&#xff0c;使您能够评估其有效性并确定它是否是您未来项目的不错选择。 htmx 是一个现代 JavaScript 库&#xff0c;旨在通过…

多叉树题目:N 叉树的前序遍历

文章目录 题目标题和出处难度题目描述要求示例数据范围进阶 解法一思路和算法代码复杂度分析 解法二思路和算法代码复杂度分析 解法三思路和算法代码复杂度分析 题目 标题和出处 标题&#xff1a;N 叉树的前序遍历 出处&#xff1a;589. N 叉树的前序遍历 难度 3 级 题目…

一文速通自监督学习(Self-supervised Learning):教机器自我探索的艺术

一文速通自监督学习&#xff08;Self-supervised Learning&#xff09;&#xff1a;教机器自我探索的艺术 前言自监督学习是什么&#xff1f;自监督学习的魔力常见的自监督学习方法1. 对比学习2. 预测缺失部分3. 旋转识别4. 时间顺序预测 结语 &#x1f308;你好呀&#xff01;…

Docker新手攻略:编辑Dockerfile、构建镜像、启动容器全攻略

万能dockerfile编写模板文件 FROM openjdk:11.0 as builder WORKDIR application ARG JAR_FILEtarget/*.jar COPY ${JAR_FILE} application.jar RUN java -Djarmodelayertools -jar application.jar extractFROM openjdk:11.0 WORKDIR application COPY --frombuilder applica…

Springboot项目结构

1. 一个正常的企业项目里一种通用的项目结构和代码层级划分的指导意见&#xff1a; 一般分为如下几层&#xff1a; 开放接口层 终端显示层 Web 层 Service 层 Manager 层 DAO 层 外部接口或第三方平台 2. 以当下非常火热的Spring Boot典型项目结构为例&#xff0c;创建出…

代码随想录算法训练营第三十四天 |1005. K 次取反后最大化的数组和 、134. 加油站、135. 分发糖果

代码随想录算法训练营第三十四天 |1005. K 次取反后最大化的数组和 、134. 加油站、135. 分发糖果 1005. K 次取反后最大化的数组和题目解法 134. 加油站题目解法 135. 分发糖果题目解法 感悟 1005. K 次取反后最大化的数组和 题目 解法 考虑绝对值 class Solution { public…