Java并发之ReentrantLock

AQS

AQS(AbstractQueuedSynchronizer):抽象队列同步器,是一种用来构建锁和同步器的框架。在是JUC下一个重要的并发类,例如:ReentrantLock、Semaphore、CountDownLatch、LimitLatch等并发都是由AQS衍生出来的。

理解

CLH队列

是一种基于链表的可扩展,高性能,公平的自旋锁。它的队列中每个节点等待前驱节点释放锁,当前置节点执行完成,才会唤醒后置节点,这样最多只有后置节点和新进入的线程(非公平锁状态下)来抢占CPU资源,其余线程处于阻塞状态,极大地减少了CPU开销(同时将多个线程唤醒时,唤醒的线程将进入RUNABLE状态,但是只有一个线程会从竞争获取到锁,而其他线程将会处于BLOCKED状态)。

CLH的出列

当node1出列时,就会把当head的指向出队的下个节点node2,同时也把node1的上下关系断开。
在这里插入图片描述

CLH的入列

队列初始化的时候,head和tail都为空。此时有节点入列,这把当前节点存放到队列尾部节点的下个节点中,在让tail指向这个新增节点,同时也会处理入队的节点的上下节点关系。
在这里插入图片描述

CAS

全称为Compare-And-Swap,它是一条CPU并发原语,也是一种乐观锁(类似数据库设计表时添加的version字段)。执行过程:如果当前状态值(内存中在stateOffset位置的值)等于预期值(expect),则自动将同步状态设置为给定的更新值(update);反之则重试(好像是10次),重试后依旧无果,那么就更新失败。

    protected final boolean compareAndSetState(int expect, int update) {// See below for intrinsics setup to support this// stateOffset指向一段内存,通过这个可以准确地告诉你某个字段相对于对象的起始内存地址的字节偏移,便于比较。return unsafe.compareAndSwapInt(this, stateOffset, expect, update);}

过程

  • 抢锁
    公平锁
    按照线程锁的添加顺序(由CLH来保证)来获取锁。

    public final void acquire(int arg) {if (!tryAcquire(arg) && // 尝试获取锁acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 获取锁失败,添加到队列中selfInterrupt();// 添加到队列成功后,就进行中断状态
    }// 返回false时,获取锁失败,返回true,获取成功
    protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState(); // 获取锁的状态stateif (c == 0) {if (!hasQueuedPredecessors() && //判断队列中是否有线程在等待compareAndSetState(0, acquires)) {// 尝试去修改锁的状态setExclusiveOwnerThread(current);//  修改成功,就把自己设置成独占访问权限return true;}} else if (current == getExclusiveOwnerThread()) { // 自旋int nextc = c + acquires;if (nextc < 0)throw new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;
    }
    

    非公平锁
    等待队列中的首位正要获取的锁的线程(没有添加到队列中),有同等的机会获取锁。

    final void lock() {if (compareAndSetState(0, 1))//尝试修改锁的状态setExclusiveOwnerThread(Thread.currentThread());// 修改成功,就把自己设置成独占访问权限elseacquire(1); // 就加入等待队列中
    }
    protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);// 已非公平锁的方式获取锁,
    }
    // 
    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) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}
    
  • 释放锁
    asdfasd

  • 入队、阻塞

      // 线程入列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; // help GCfailed = 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)// 正常阻塞的节点 SIGNAL=-1/** This node has already set status asking a release* to signal it, so it can safely park.*/return true;if (ws > 0) {// 节点状态错误,删除掉这样的节点/** Predecessor was cancelled. Skip over predecessors and* indicate retry.*/do {node.prev = pred = pred.prev;} while (pred.waitStatus > 0);pred.next = node;} 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);// 将前置节点修改位-1,表示后续节点将被阻塞。}return false;
    }// 队列是否有前置线程   
    public final boolean hasQueuedPredecessors() {// The correctness of this depends on head being initialized// before tail and on head.next being accurate if the current// thread is first in queue.Node t = tail; // Read fields in reverse initialization orderNode h = head;Node s;return h != t &&((s = h.next) == null || s.thread != Thread.currentThread());// h = t时,队列中至少有两个队列(未初始化的时候,h,t都为null,说明没有前执行的线程,返回false;如多队列中只有一个线程的时候,h=t, 这个线程一定正在执行,返回false;队列中存在多个时,// h != t时,(s = h.next) == null 这个条件可能是为了在end()中,给head设置new Node时,还未将head赋值给tail时做的处理,s.thread != Thread.currentThread()用于判断是不是自旋)
    }// 添加到队列中
    private Node addWaiter(Node mode) {Node node = new Node(Thread.currentThread(), mode);// Try the fast path of enq; backup to full enq on failureNode pred = tail;if (pred != null) {// 尾节点不是空花,就添加到最后node.prev = pred;if (compareAndSetTail(pred, node)) {// 把当前节点设置成尾节点pred.next = node;// 把原来的未节点的下分节点设置成当前添加的节点return node;}}enq(node);// 兜底的手段:1)一定要把当前节点添加到队列中 2)对位未初始化,初始化队列中return node;
    }
    // 通过死循环添加到队列中private Node enq(final Node node) {for (;;) {Node t = tail;if (t == null) { // Must initializeif (compareAndSetHead(new Node()))// 头节未nul时,增加一个空的新节点tail = head;// 让尾节点等于头节点} else {node.prev = t;if (compareAndSetTail(t, node)) {// 把当前节点添加到未节点t.next = node;return t;}}}
    }
    
  • 唤醒

// 释放锁
public void unlock() {sync.release(1);
}
// 释放锁的具体实现
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) {// 为0是,说明锁处于空闲状态free = true;setExclusiveOwnerThread(null);// 修改当前获取锁的线程为空}setState(c);// 修改锁的状态return free;// c != 0时,说明自选还未完成,释放锁失败。
}// 唤醒下个节点
private void unparkSuccessor(Node node) {/** If status is negative (i.e., possibly needing signal) try* to clear in anticipation of signalling.  It is OK if this* fails or if status is changed by waiting thread.*/int ws = node.waitStatus;if (ws < 0)// 节点的线程状态处于等待中,就更新为0.compareAndSetWaitStatus(node, ws, 0);/** Thread to unpark is held in successor, which is normally* just the next node.  But if cancelled or apparently null,* traverse backwards from tail to find the actual* non-cancelled successor.*/Node s = node.next;if (s == null || s.waitStatus > 0) {// 线程可能超时,或者中断了,无法在唤醒了s = null;for (Node t = tail; t != null && t != node; t = t.prev)// 从队列尾部开始唤醒可以获取锁的线程,直到上一个完成的线程(t).这里从尾部开始的原因可能是越早的线程越容易超时(中断),所以从尾部获取比较快速。if (t.waitStatus <= 0)// 线程状态必须<=0。由于线程在由初始化0变成-1时,不是一个原子,所以这里位<=0s = t;}if (s != null)//  当线程存在时,唤醒线程LockSupport.unpark(s.thread);
}
  • 出列
    在添加队列的时候调用acquireQueued时,会将head出列,交给gc回收
    在这里插入图片描述

机制

  • state
    锁的状态:
    • 0:锁处于空闲状态
    • >=1: 已经有线程获取到锁了(大于1 自选)
	 /*** The synchronization state.*/private volatile int state;
  • waitState
    线程状态:
    • -1:表示该节点的后继节点被阻塞(或即将被阻塞),因此当前节点在释放或取消时必须解除其后继节点的阻塞。
    • 0:初始化的状态
    • -2:条件状态(暂时不清楚)
    • >=1:表示由于超时或中断,此节点被取消,节点永远不会离开这种状态。取消了节点的线程不会再阻塞其它线程。

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

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

相关文章

React Native Expo项目,复制文本到剪切板

装包&#xff1a; npx expo install expo-clipboard import * as Clipboard from expo-clipboardconst handleCopy async (text) > {await Clipboard.setStringAsync(text)Toast.show(复制成功, {duration: 3000,position: Toast.positions.CENTER,})} 参考链接&#xff1a…

3.文件目录

第四章 文件管理 3.文件目录 ​   对于D盘这个根目录来说它对应的目录文件就是图中的样子&#xff0c;其实就是用一个所谓的目录表来表示这个目录下面存放了哪些东西。在D盘中的每一个文件&#xff0c;每一个文件夹都会对应这个目录表中的一个表项&#xff0c;所以其实这些一…

Autoware感知02—欧氏聚类(lidar_euclidean_cluster_detect)源码解析

文章目录 引言一、点云回调函数&#xff1a;二、预处理&#xff08;1&#xff09;裁剪距离雷达过于近的点云&#xff0c;消除车身的影响&#xff08;2&#xff09;点云降采样&#xff08;体素滤波&#xff0c;默认也是不需要的&#xff09;&#xff08;3&#xff09;裁剪雷达高…

【概念篇】文件概述

✅作者简介&#xff1a;大家好&#xff0c;我是小杨 &#x1f4c3;个人主页&#xff1a;「小杨」的csdn博客 &#x1f433;希望大家多多支持&#x1f970;一起进步呀&#xff01; 文件概述 1&#xff0c;文件的概念 狭义上的文件是计算机系统中用于存储和组织数据的一种数据存…

React源码解析18(5)------ 实现函数组件【修改beginWork和completeWork】

摘要 经过之前的几篇文章&#xff0c;我们实现了基本的jsx&#xff0c;在页面渲染的过程。但是如果是通过函数组件写出来的组件&#xff0c;还是不能渲染到页面上的。 所以这一篇&#xff0c;主要是对之前写得方法进行修改&#xff0c;从而能够显示函数组件&#xff0c;所以现…

【深度学习】NLP中的对抗训练

在NLP中&#xff0c;对抗训练往往都是针对嵌入层&#xff08;包括词嵌入&#xff0c;位置嵌入&#xff0c;segment嵌入等等&#xff09;开展的&#xff0c;思想很简单&#xff0c;即针对嵌入层添加干扰&#xff0c;从而提高模型的鲁棒性和泛化能力&#xff0c;下面结合具体代码…

Spark 学习记录

基础 SparkContext是什么&#xff1f;有什么作用&#xff1f; https://blog.csdn.net/Shockang/article/details/118344357 SparkContext 是什么&#xff1f; SparkContext 是通往 Spark 集群的唯一入口&#xff0c;可以用来在 Spark 集群中创建 RDDs 、累加和广播变量( Br…

【数据库基础】Mysql下载安装及配置

下载 下载地址&#xff1a;https://downloads.mysql.com/archives/community/ 当前最新版本为 8.0版本&#xff0c;可以在Product Version中选择指定版本&#xff0c;在Operating System中选择安装平台&#xff0c;如下 安装 MySQL安装文件分两种 .msi和.zip [外链图片转存失…

C++11时间日期库chrono的使用

chrono是C11中新加入的时间日期操作库&#xff0c;可以方便地进行时间日期操作&#xff0c;主要包含了&#xff1a;duration, time_point, clock。 时钟与时间点 chrono中用time_point模板类表示时间点&#xff0c;其支持基本算术操作&#xff1b;不同时钟clock分别返回其对应…

Docker中部署Nginx

1.Nginx部署需求 2.操作教程 3.实际步骤 把配置粘过来。

Cookie、Session、Token的区别

有人或许还停留在它们只是验证身份信息的机制&#xff0c;但是它们之间的关系你真的弄懂了么&#xff1f; 发展史&#xff1a; Coolie: Netscape Communications 公司引入了 Cookie 概念&#xff0c;作为在客户端存储状态信息的一种方法。初始目的是为了解决 HTTP 的无状态性…

Python爬虫:单线程、多线程、多进程

前言 在使用爬虫爬取数据的时候&#xff0c;当需要爬取的数据量比较大&#xff0c;且急需很快获取到数据的时候&#xff0c;可以考虑将单线程的爬虫写成多线程的爬虫。下面来学习一些它的基础知识和代码编写方法。 一、进程和线程 进程可以理解为是正在运行的程序的实例。进…

python爬虫数据解析xpath、jsonpath,bs4

数据的解析 解析数据的方式大概有三种 xpathJsonPathBeautifulSoup xpath 安装xpath插件 打开谷歌浏览器扩展程序&#xff0c;打开开发者模式&#xff0c;拖入插件&#xff0c;重启浏览器&#xff0c;ctrlshiftx&#xff0c;打开插件页面 安装lxml库 安装在python环境中的Scri…

并发服务器模型,多线程并发

一、多线程并发完整代码 #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <string.h> #include <unistd.h> #include <sys/wait.h> #include <stdlib.h> #include <…

突然让做性能测试?试试RunnerGo

当前&#xff0c;性能测试已经是一名软件测试工程师必须要了解&#xff0c;甚至熟练使用的一项技能了&#xff0c;在工作时可能每次发版都要跑一遍性能&#xff0c;跑一遍自动化。性能测试入门容易&#xff0c;深入则需要太多的知识量&#xff0c;今天这篇文章给大家带来&#…

Rocky Linux更换为国内源

Rocky Linux提供的可供切换的源列表&#xff1a;Mirrors - Mirror Manager 其中以 COUNTRY 列为 CN 的是国内源。 选择其中一个Rocky Linux 源使用帮助 — USTC Mirror Help 文档 操作前请做好备份 对于 Rocky Linux 8&#xff0c;使用以下命令替换默认的配置 sed -e s|^mirr…

新能源汽车电控系统

新能源汽车电控系统主要分为&#xff1a;三电系统电控系统、高压系统电控系统、低压系统电控系统 三电系统电控系统 包括整车控制器、电池管理系统、驱动电机控制器等。 整车控制器VCU 整车控制器作为电动汽车中央控制单元&#xff0c;是整个控制系统的核心&#xff0c;也是…

zabbix监控mysql数据库、nginx、Tomcat

zabbix监控mysql数据库、nginx、Tomcat 一.zabbix监控mysql数据库 1.环境规划 hostIP部署zabbix-server192.168.198.17zabbix服务器搭建zabbix-mysql192.168.198.15zabbix客户端搭建 2.zabbix-server安装部署&#xff08;192.168.198.17&#xff09; 请参考以下配置&#…

Azure概念介绍

云计算定义 云计算是一种使用网络进行存储和处理数据的计算方式。它通过将数据和应用程序存储在云端服务器上&#xff0c;使用户能够通过互联网访问和使用这些资源&#xff0c;而无需依赖于本地硬件和软件。 发展历史 云计算的概念最早可以追溯到20世纪60年代的时候&#x…

年至年的选择仿elementui的样式

组件&#xff1a;<!--* Author: liuyu liuyuxizhengtech.com* Date: 2023-02-01 16:57:27* LastEditors: wangping wangpingxizhengtech.com* LastEditTime: 2023-06-30 17:25:14* Description: 时间选择年 - 年 --> <template><div class"yearPicker"…