JAVA并发编程: AQS获取与释放

浅谈Java的AQS 所谓AQS,指的是AbstractQueuedSynchronizer,它提供了一种实现阻塞锁和一系列依赖FIFO等待队列的同步器的框架,ReentrantLock、Semaphore、CountDownLatch、CyclicBarrier等并发类均是基于AQS来实现的,具体用法是通过继承AQS实现其模板方法,然后将子类作为同步组件的内部类。 了解一个框架最好的方式是读源码,说干就干。 AQS是... 阅读详情

同步状态

AQS采用的是CLH队列,CLH队列是由一个一个结点构成的,前面提到结点中有一个状态位,这个状态位与线程状态密切相关,这个状态位(waitStatus)是一个32位的整型常量,它的取值如下:

[java]  view plain  copy
 print ?
  1. static final int CANCELLED =  1;  
  2. static final int SIGNAL    = -1;  
  3. static final int CONDITION = -2;  
  4. static final int PROPAGATE = -3;  
下面解释一下每个值的含义

CANCELLED:因为超时或者中断,结点会被设置为取消状态,被取消状态的结点不应该去竞争锁,只能保持取消状态不变,不能转换为其他状态。处于这种状态的结点会被踢出队列,被GC回收;

SIGNAL:表示这个结点的继任结点被阻塞了,到时需要通知它;

CONDITION:表示这个结点在条件队列中,因为等待某个条件而被阻塞;

PROPAGATE:使用在共享模式头结点有可能牌处于这种状态,表示锁的下一次获取可以无条件传播;

0:None of the above,新结点会处于这种状态。


获取

AQS中比较重要的两个操作是获取和释放,以下是各种获取操作:

[java]  view plain  copy
 print ?
  1. public final void acquire(int arg);  
  2. public final void acquireInterruptibly(int arg);  
  3. public final void acquireShared(int arg);  
  4. public final void acquireSharedInterruptibly(int arg);  
  5. protected boolean tryAcquire(int arg);   
  6. protected int tryAcquireShared(int arg);  
  7. public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException;  
  8. public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException;       
获取操作的流程图如下:

1、如果尝试获取锁成功整个获取操作就结束,否则转到2. 尝试获取锁是通过方法tryAcquire来实现的,AQS中并没有该方法的具体实现,只是简单地抛出一个不支持操作异常,在AQS简介中谈到tryAcquire有很多实现方法,这里不再细化,只需要知道如果获取锁成功该方法返回true即可;

2、如果获取锁失败,那么就创建一个代表当前线程的结点加入到等待队列的尾部,是通过addWaiter方法实现的,来看该方法的具体实现:

[java]  view plain  copy
 print ?
  1. /** 
  2.  * Creates and enqueues node for current thread and given mode. 
  3.  * 
  4.  * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared 
  5.  * @return the new node 
  6.  */  
  7. private Node addWaiter(Node mode) {  
  8.     Node node = new Node(Thread.currentThread(), mode);  
  9.     // Try the fast path of enq; backup to full enq on failure  
  10.     Node pred = tail;  
  11.     if (pred != null) {  
  12.         node.prev = pred;  
  13.         if (compareAndSetTail(pred, node)) {  
  14.             pred.next = node;  
  15.             return node;  
  16.         }  
  17.     }  
  18.     enq(node);  
  19.     return node;  
  20. }  
该方法创建了一个独占式结点,然后判断队列中是否有元素,如果有(pred!=null)就设置当前结点为队尾结点,返回;

如果没有元素(pred==null),表示队列为空,走的是入队操作

[java]  view plain  copy
 print ?
  1. /** 
  2.  * Inserts node into queue, initializing if necessary. See picture above. 
  3.  * @param node the node to insert 
  4.  * @return node's predecessor 
  5.  */  
  6. private Node enq(final Node node) {  
  7.     for (;;) {  
  8.         Node t = tail;  
  9.         if (t == null) { // Must initialize  
  10.             if (compareAndSetHead(new Node()))  
  11.                 tail = head;  
  12.         } else {  
  13.             node.prev = t;  
  14.             if (compareAndSetTail(t, node)) {  
  15.                 t.next = node;  
  16.                 return t;  
  17.             }  
  18.         }  
  19.     }  
  20. }  
enq方法采用的是变种CLH算法,先看头结点是否为空,如果为空就创建一个傀儡结点,头尾指针都指向这个傀儡结点,这一步只会在队列初始化时会执行;

如果头结点非空,就采用CAS操作将当前结点插入到头结点后面,如果在插入的时候尾结点有变化,就将尾结点向后移动直到移动到最后一个结点为止,然后再把当前结点插入到尾结点后面,尾指针指向当前结点,入队成功。

3、将新加入的结点放入队列之后,这个结点有两种状态,要么获取锁,要么就挂起,如果这个结点不是头结点,就看看这个结点是否应该挂起,如果应该挂起,就挂起当前结点,是否应该挂起是通过shouldParkAfterFailedAcquire方法来判断的

[java]  view plain  copy
 print ?
  1. /** 
  2.     * Checks and updates status for a node that failed to acquire. 
  3.     * Returns true if thread should block. This is the main signal 
  4.     * control in all acquire loops.  Requires that pred == node.prev 
  5.     * 
  6.     * @param pred node's predecessor holding status 
  7.     * @param node the node 
  8.     * @return {@code true} if thread should block 
  9.     */  
  10.    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {  
  11.        int ws = pred.waitStatus;  
  12.        if (ws == Node.SIGNAL)  
  13.            /* 
  14.             * This node has already set status asking a release 
  15.             * to signal it, so it can safely park. 
  16.             */  
  17.            return true;  
  18.        if (ws > 0) {  
  19.            /* 
  20.             * Predecessor was cancelled. Skip over predecessors and 
  21.             * indicate retry. 
  22.             */  
  23.            do {  
  24.                node.prev = pred = pred.prev;  
  25.            } while (pred.waitStatus > 0);  
  26.            pred.next = node;  
  27.        } else {  
  28.            /* 
  29.             * waitStatus must be 0 or PROPAGATE.  Indicate that we 
  30.             * need a signal, but don't park yet.  Caller will need to 
  31.             * retry to make sure it cannot acquire before parking. 
  32.             */  
  33.            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);  
  34.        }  
  35.        return false;  
  36.    }  
该方法首先检查前趋结点的waitStatus位,如果为SIGNAL,表示前趋结点会通知它,那么它可以放心大胆地挂起了;

如果前趋结点是一个被取消的结点怎么办呢?那么就向前遍历跳过被取消的结点,直到找到一个没有被取消的结点为止,将找到的这个结点作为它的前趋结点,将找到的这个结点的waitStatus位设置为SIGNAL,返回false表示线程不应该被挂起。
上面谈的不是头结点的情况决定是否应该挂起,是头结点的情况呢?

是头结点的情况,当前线程就调用tryAcquire尝试获取锁,如果获取成功就将头结点设置为当前结点,返回;如果获取失败就循环尝试获取锁,直到获取成功为止。整个acquire过程就分析完了。


释放

释放操作有以下方法:

[java]  view plain  copy
 print ?
  1. public final boolean release(int arg);   
  2. protected boolean tryRelease(int arg);   
  3. protected boolean tryReleaseShared(int arg);   

下面看看release方法的实现过程


1、release过程比acquire要简单,首先调用tryRelease释放锁,如果释放失败,直接返回;

2、释放锁成功后需要唤醒继任结点,是通过方法unparkSuccessor实现的

[java]  view plain  copy
 print ?
  1. /** 
  2.     * Wakes up node's successor, if one exists. 
  3.     * 
  4.     * @param node the node 
  5.     */  
  6.    private void unparkSuccessor(Node node) {  
  7.        /* 
  8.         * If status is negative (i.e., possibly needing signal) try 
  9.         * to clear in anticipation of signalling.  It is OK if this 
  10.         * fails or if status is changed by waiting thread. 
  11.         */  
  12.        int ws = node.waitStatus;  
  13.        if (ws < 0)  
  14.            compareAndSetWaitStatus(node, ws, 0);  
  15.   
  16.        /* 
  17.         * Thread to unpark is held in successor, which is normally 
  18.         * just the next node.  But if cancelled or apparently null, 
  19.         * traverse backwards from tail to find the actual 
  20.         * non-cancelled successor. 
  21.         */  
  22.        Node s = node.next;  
  23.        if (s == null || s.waitStatus > 0) {  
  24.            s = null;  
  25.            for (Node t = tail; t != null && t != node; t = t.prev)  
  26.                if (t.waitStatus <= 0)  
  27.                    s = t;  
  28.        }  
  29.        if (s != null)  
  30.            LockSupport.unpark(s.thread);  
  31.    }  
1、node参数传进来的是头结点,首先检查头结点的waitStatus位,如果为负,表示头结点还需要通知后继结点,这里不需要头结点去通知后继,因此将该该标志位清0.

2、然后查看头结点的下一个结点,如果下一个结点不为空且它的waitStatus<=0,表示后继结点没有被取消,是一个可以唤醒的结点,于是唤醒后继结点返回;如果后继结点为空或者被取消了怎么办?寻找下一个可唤醒的结点,然后唤醒它返回。

这里并没有从头向尾寻找,而是相反的方向寻找,为什么呢?

因为在CLH队列中的结点随时有可能被中断,被中断的结点的waitStatus设置为CANCEL,而且它会被踢出CLH队列,如何个踢出法,就是它的前趋结点的next并不会指向它,而是指向下一个非CANCEL的结点,而它自己的next指针指向它自己。一旦这种情况发生,如何从头向尾方向寻找继任结点会出现问题,因为一个CANCEL结点的next为自己,那么就找不到正确的继任接点。

有的人又会问了,CANCEL结点的next指针为什么要指向它自己,为什么不指向真正的next结点?为什么不为NULL?

第一个问题的答案是这种被CANCEL的结点最终会被GC回收,如果指向next结点,GC无法回收。

对于第二个问题的回答,JDK中有这么一句话: The next field of cancelled nodes is set to point to the node itself instead of null, to make life easier for isOnSyncQueue.大至意思是为了使isOnSyncQueue方法更新简单。isOnSyncQueue方法判断一个结点是否在同步队列,实现如下:

[java]  view plain  copy
 print ?
  1. /** 
  2.  * Returns true if a node, always one that was initially placed on 
  3.  * a condition queue, is now waiting to reacquire on sync queue. 
  4.  * @param node the node 
  5.  * @return true if is reacquiring 
  6.  */  
  7. final boolean isOnSyncQueue(Node node) {  
  8.     if (node.waitStatus == Node.CONDITION || node.prev == null)  
  9.         return false;  
  10.     if (node.next != null) // If has successor, it must be on queue  
  11.         return true;  
  12.     /* 
  13.      * node.prev can be non-null, but not yet on queue because 
  14.      * the CAS to place it on queue can fail. So we have to 
  15.      * traverse from tail to make sure it actually made it.  It 
  16.      * will always be near the tail in calls to this method, and 
  17.      * unless the CAS failed (which is unlikely), it will be 
  18.      * there, so we hardly ever traverse much. 
  19.      */  
  20.     return findNodeFromTail(node);  
  21. }  
如果一个结点next不为空,那么它在同步队列中,如果CANCEL结点的后继为空那么CANCEL结点不在同步队列中,这与事实相矛盾。

因此将CANCEL结点的后继指向它自己是合理的选择。


参考资料:


Unity3d实现红外热成像效果 1.将需要在红外图像中高亮的物体设置到图层PostProcessing。 2.新建一个相机CameraHighLight,设置其Culling Mask为PostProcessing,也就是在这个相机中只有PostProcessing图层的物体可见。 3.新建一个RenderTexture,命名为HightLightRt,将CameraHighLight的Target Texture设HightLightRt。 4.新建一个相机Camera2,这个相机用于显示其他物体。 接下来将CameraHigh 阅读详情

相关推荐

头歌操作系统 课后作业4.3:段页式内存管理 答案 无解析

头歌操作系统 课后作业4.3:段页式内存管理 答案 无解析

kercii的博客 7341

Java AQS unparkSuccessor 方法中for循环为什么是从tail开始而不是head

https://blog.youkuaiyun.com/foxException/article/details/108917338 目录 1、前言2、尾部遍历源码3、高并发下入队逻辑3.1 原子性问题3.2 图解流程 4、结尾 1、前言 在AQS多线程竞争下的获取与释放流程

yygr的博客 628

maya权重拷贝一对一,一对多

是不是有时候盔甲很多很碎头很大,用代理一个个拷贝很麻烦吧 用下面代码直接运行进行一对多或者一对一拷贝 第一个先选择参考物体,加选其他一堆有的没的有权重或没权重的物体执行一键拷贝,就可以啦 以下是源码送上,带走吧 ------------------------ #! -*- coding:utf -8 -*- #author:liubingdong #date:20...

weixin_30852367的博客 1532

Java抽象队列同步器源码分析(AQS)

文章目录AQS1. AQS 概述2. AQS 的数据结构3. 资源共享模式4. AQS 的主要源码解析4.1 获取资源4.2 释放资源 深入理解 synchronized 关键字(关于对象头和锁的升级) 深入理解 volatile 关键字(内存屏障和指令重排问题) AQS 1. AQS 概述 AQS是 AbstractQueuedSynchronizer 的简称,即 抽象队列同步器 ,从字⾯意思上理解: 抽象:抽象类,只实现⼀些主要逻辑,有些⽅法由⼦类实现; 队列:使⽤先进先出(FIFO)队列存储数据;

Mr_changxin的博客 1197

单路径微分相除和微分自乘相减算法(PGC-SDD-DSM)

PGC-SDD-DSM算法通过融合单路微分相除和差分自乘,解决了传统PGC解调技术对调制深度和光强干扰敏感的问题,显著提升了解调稳定性和抗失真能力。其高信噪比、低谐波失真以及对参数变化的鲁棒性,使其在光纤传感领域具有重要应用价值。PGC-SDD-DSM算法在计算复杂度方面具有显著的优势,主要体现在其高稳定性和低谐波失真上。这些优势使其在光纤光学干涉传感器中表现出色,尤其是在面对光强干扰和调制深度变化时。

百态·数智思维 | 聚焦大数据、量化与未来AI 839

重磅!AI实时读取总结微信聊天记录,附MCP协议+智能体配置教程!

【摘要】本文介绍了一套AI实时分析微信聊天记录的解决方案,通过Chatlog+Trae+MCP协议实现智能整理群聊信息。Chatlog作为开源工具本地解析微信数据,确保隐私安全;Trae提供AI分析能力,支持自然语言处理。操作步骤包括:1)下载运行Chatlog并启动HTTP服务;2)安装Trae配置MCP协议;3)创建智能体设置提示词。该方案虽需初始配置,但能有效解决群聊信息过载问题,自动提炼关键内容,避免有价值信息被淹没。文中提供了详细教程链接和网盘资源。

puyihuan的博客 1万+

AQS底层原理及源码分析详解

AQS底层原理及源码分析详解 一. AQS是什么 队列同步器AbstractQueuedSynchronizer(简称为AQS),是用来构建锁或者其他同步组件的基础框架,通过内置的FIFO(先来先服务)队列来完成资源获取线程的排队工作。AQS是实现锁的关键,简单理解两者的关系就是:锁是面向使用者的;AQS面向的是锁的实现者,简化了锁的实现方式,屏蔽了同步状态管理,线程排队,等待唤醒底层操作的细节,对外放出模板方法供子类实现。 二. AQS底层原理 AQS内部用一个volatile修饰的int类型的成员变量s

m0_46566541的博客 4354

ESP32+LED12864液晶屏实战:手把手教你显示中文与自定义图标(附完整代码)

本文详细介绍了如何使用ESP32单片机驱动带字库的LED12864液晶屏,实现中文显示与自定义图标。从硬件选型、接线方案到驱动编写、字库解析,提供了完整的实战代码与优化技巧,并最终构建了一个智能家居控制面板的完整示例项目。

weixin_29212867的博客 145

AQS源码级别解析

顾名思义就是一个抽象的(可被继承复用),内部存在排队(竞争资源的线程排队)的同步器(对共享资源和线程进行同步管理)。

qq_73360961的博客 1212

2021年J题:基于STM32F103的周期信号波形识别与参数测量系统设计(立创EDA+SZPT_ZXL)

本文详细复盘了2021年全国大学生电子设计竞赛J题获奖项目,手把手教你如何基于STM32F103微控制器设计并实现周期信号波形识别与参数测量系统。文章深入剖析了硬件电路设计(包括程控放大、工作点抬升与滞回比较器)与核心软件算法(如动态采样、FFT频谱分析与波形识别逻辑),并分享了DMA传输、FFT库使用等实战要点与避坑指南,为嵌入式信号处理与电赛备赛提供了宝贵经验。

weixin_32673065的博客 227

OpenClaw 生成测试用例

OpenClaw 只是躯干,Skills 才是灵魂。很多同学觉得 OpenClaw 没用,就像是买了一台顶级电脑却没装操作系统。通过这四步走,不仅可以学会如何让 AI 干活,更重要的是,可以学会如何管理 AI 的能力。

十二测试录 737

【Java并发编程】AQS(4)——共享锁的获取与释放

今天来说下共享锁的获取与释放,建议大家在看这篇文章之前,先将我写的关于独占锁的文章看一下,其中涉及了许多重复的方法,在这篇文章中就不会再次讲解了。好了,我们先来看共享锁的获取吧 一. 共享锁的获取 在AQS中共享锁的获取一共有三个方法,今天主要讲第一个 acquireShared:不响应中断获取锁 acquireSharedInterruptibly:响应中...

哈喽羊 876

一种基于机载LiDAR点云电力线自动提取方法

文章目录一、引言二、算法实现2.1实现步骤2.2实现代码 一、引言 二、算法实现 2.1实现步骤 (1)首先采用首先采用高程滤波方法去除地面点和低矮地物点,滤波后的点云数据中除电力线点云外还含部分杆塔点、高植被点和噪声点。 (2)根据点云空间维度特征提取电力线点,使用空间密度聚类的方法实现单根电力线精确分离。 2.2实现代码 在这里插入代码片 ...

dayuhaitang1的博客 5170

【Java并发编程实战】----- AQS(二):获取锁、释放锁

http://www.cnblogs.com/chenssy/p/5073857.html 上篇博客稍微介绍了一下AQS,下面我们来关注下AQS的所获取和锁释放。 AQS锁获取 AQS包含如下几个方法: acquire(int arg):以独占模式获取对象,忽略中断。 acquireInterruptibly(int arg): 以独占模式获取对象,如果被中断则中

zdy0_2004的专栏 653

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

Java并发编程——AQS之独占式同步状态的获取与释放

这里写目录标题独占式获取acquirevoid acquire(int arg)独占式释放release二级目录三级目录 独占式获取acquire void acquire(int arg) /** *这里的acquire方法目的就是在获取失败时,能够自旋 *这里的if判断就实现了这一功能 *如果想要产生自旋,那么就要同时满足if的两个条件 *1.tryAcquire(arg)失败,就是说此时尝试获取当前节点失败 *2.同时,acquireQueued成功,就是。 * */ public final voi

java臭虫的博客 403

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

Java并发编程--AQS的锁获取和释放

/** * acquire(int arg):以独占模式获取对象,忽略中断。 acquireInterruptibly(int arg): 以独占模式获取对象,如果被中断则中止。 acquireShared(int arg): 以共享模式获取对象,忽略中断。 acquireSharedInterruptibly(int arg)以共享模式获取对象...

Mr long的博客 485

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

Java并发编程——AQS之共享式同步状态的获取与释放

这里写目录标题共享式获取void acquireShared(int arg)void doAcquireShared(int arg)全图共享式释放boolean releaseShared(int arg) 共享式获取 void acquireShared(int arg) //同步器调用tryAcquireShared获取同步状态,当返回值大于0 时,表示能够获取同步状态。 public final void acquireShared(int arg) { if (tryAcquir

java臭虫的博客 659

Java并发编程 AQS分析(二):获取锁、释放锁

上篇博客稍微介绍了一下AQS,下面我们来关注下AQS的所获取和锁释放。 AQS锁获取 AQS包含如下几个方法: acquire(int arg):以独占模式获取对象,忽略中断。 acquireInterruptibly(int arg): 以独占模式获取对象,如果被中断则中止。 acquireShared(int arg): 以共享模式获取对象,忽略中断。 ac

大树叶 技术专栏 743

JAVA并发编程学习笔记之AQS源码分析(获取与释放)

同步状态 AQS采用的是CLH队列,CLH队列是由一个一个结点构成的,前面提到结点中有一个状态位,这个状态位与线程状态密切相关,这个状态位(waitStatus)是一个32位的整型常量,它的取值如下: [java] view plaincopyprint? static final int CANCELLED =  1;   static

Stereo 744

java释放锁_【Java并发编程实战】- AQS(二):获取锁、释放锁 - Java 技术驿站-Java 技术驿站...

上篇博客稍微介绍了一下AQS,下面我们来关注下AQS的所获取和锁释放。AQS锁获取AQS包含如下几个方法:acquire(int arg):以独占模式获取对象,忽略中断。acquireInterruptibly(int arg): 以独占模式获取对象,如果被中断则中止。acquireShared(int arg): 以共享模式获取对象,忽略中断。acquireSharedInterruptibly...

weixin_32514497的博客 335

【Java并发编程实战】-----“J.U.C”:AQS分析(二):获取锁、释放锁

上篇博客稍微介绍了一下AQS,下面我们来关注下AQS的所获取和锁释放。AQS锁获取AQS包含如下几个方法:acquire(int arg):以独占模式获取对象,忽略中断。acquireInterruptibly(int arg): 以独占模式获取对象,如果被中断则中止。acquireShared(int arg): 以共享模式获取对象,忽略中断。acquireSharedInterruptibly

chenssy 的技术博客 6477

Java并发编程实战---- AQS(二):获取锁、释放锁

下面我们来关注下AQS的所获取和锁释放。 AQS锁获取 AQS包含如下几个方法: acquire(int arg):以独占模式获取对象,忽略中断。 acquireInterruptibly(int arg): 以独占模式获取对象,如果被中断则中止。 acquireShared(int arg): 以共享模式获取对象,忽略中断。 acquireSharedInte

大树叶 技术专栏 423
上一篇: Java的LockSupport.park()实现分析
下一篇: Java并发编程 AQS分析(二):获取锁、释放锁
大树叶
博客等级 码龄12年 1171粉丝 664原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值