前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Java 并发:队列同步器是什么?

Java 并发:队列同步器是什么?

作者头像
二哥聊运营工具
发布于 2021-12-17 02:33:11
发布于 2021-12-17 02:33:11
33300
代码可运行
举报
文章被收录于专栏:程序员泥瓦匠程序员泥瓦匠
运行总次数:0
代码可运行

来自:泥瓦匠@bysocket.com

本文目录
  • 一、什么是 AQS 队列同步器
  • 二、什么是 CLH 同步队列
  • 三、小结

什么是 AQS ?

Java的内置锁一直都是备受争议的,在JDK 1.6之前,synchronized这个重量级锁其性能一直都是较为低下,虽然在1.6后,进行大量的锁优化策略,但是与Lock相比synchronized还是存在一些缺陷的:虽然synchronized提供了便捷性的隐式获取锁释放锁机制(基于JVM机制),但是它却缺少了获取锁与释放锁的可操作性,可中断、超时获取锁,且它为独占式在高并发场景下性能大打折扣。

在介绍Lock之前,我们需要先熟悉一个非常重要的组件,掌握了该组件JUC包下面很多问题都不在是问题了。该组件就是AQS。

AQS,AbstractQueuedSynchronizer,即队列同步器。它是构建锁或者其他同步组件的基础框架(如ReentrantLock、ReentrantReadWriteLock、Semaphore等),JUC并发包的作者(Doug Lea)期望它能够成为实现大部分同步需求的基础。它是JUC并发包中的核心基础组件。

AQS解决了子啊实现同步器时涉及当的大量细节问题,例如获取同步状态、FIFO同步队列。基于AQS来构建同步器可以带来很多好处。它不仅能够极大地减少实现工作,而且也不必处理在多个位置上发生的竞争问题。

在基于AQS构建的同步器中,只能在一个时刻发生阻塞,从而降低上下文切换的开销,提高了吞吐量。同时在设计AQS时充分考虑了可伸缩行,因此J.U.C中所有基于AQS构建的同步器均可以获得这个优势。

AQS的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态。

AQS使用一个int类型的成员变量state来表示同步状态,当state>0时表示已经获取了锁,当state = 0时表示释放了锁。它提供了三个方法(getState()、setState(int newState)、compareAndSetState(int expect,int update))来对同步状态state进行操作,当然AQS可以确保对state的操作是安全的。

AQS通过内置的FIFO同步队列来完成资源获取线程的排队工作,如果当前线程获取同步状态失败(锁)时,AQS则会将当前线程以及等待状态等信息构造成一个节点(Node)并将其加入同步队列,同时会阻塞当前线程,当同步状态释放时,则会把节点中的线程唤醒,使其再次尝试获取同步状态。

AQS主要提供了如下一些方法:

  • getState():返回同步状态的当前值;
  • setState(intnewState):设置当前同步状态;
  • compareAndSetState(intexpect,intupdate):使用CAS设置当前状态,该方法能够保证状态设置的原子性;
  • tryAcquire(intarg):独占式获取同步状态,获取同步状态成功后,其他线程需要等待该线程释放同步状态才能获取同步状态;
  • tryRelease(intarg):独占式释放同步状态;
  • tryAcquireShared(intarg):共享式获取同步状态,返回值大于等于0则表示获取成功,否则获取失败;
  • tryReleaseShared(intarg):共享式释放同步状态;
  • isHeldExclusively():当前同步器是否在独占式模式下被线程占用,一般该方法表示是否被当前线程所独占;
  • acquire(intarg):独占式获取同步状态,如果当前线程获取同步状态成功,则由该方法返回,否则,将会进入同步队列等待,该方法将会调用可重写的tryAcquire(int arg)方法;
  • acquireInterruptibly(intarg):与acquire(int arg)相同,但是该方法响应中断,当前线程为获取到同步状态而进入到同步队列中,如果当前线程被中断,则该方法会抛出InterruptedException异常并返回;
  • tryAcquireNanos(intarg,longnanos):超时获取同步状态,如果当前线程在nanos时间内没有获取到同步状态,那么将会返回false,已经获取则返回true;
  • acquireShared(intarg):共享式获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待,与独占式的主要区别是在同一时刻可以有多个线程获取到同步状态;
  • acquireSharedInterruptibly(intarg):共享式获取同步状态,响应中断;
  • tryAcquireSharedNanos(intarg,longnanosTimeout):共享式获取同步状态,增加超时限制;
  • release(intarg):独占式释放同步状态,该方法会在释放同步状态之后,将同步队列中第一个节点包含的线程唤醒;
  • releaseShared(intarg):共享式释放同步状态;

那什么是 CLH同步队列?

在上面提到了AQS内部维护着一个FIFO队列,该队列就是CLH同步队列。

CLH同步队列是一个FIFO双向队列,AQS依赖它来完成同步状态的管理,当前线程如果获取同步状态失败时,AQS则会将当前线程已经等待状态等信息构造成一个节点(Node)并将其加入到CLH同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点唤醒(公平锁),使其再次尝试获取同步状态。

在CLH同步队列中,一个节点表示一个线程,它保存着线程的引用(thread)、状态(waitStatus)、前驱节点(prev)、后继节点(next),其定义如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
static final class Node {
 
 /** 共享 */
 
 static final Node SHARED = new Node();
 


 
 /** 独占 */
 
 static final Node EXCLUSIVE = null;
 


 
 /**
 
     * 因为超时或者中断,节点会被设置为取消状态,被取消的节点时不会参与到竞争中的,他会一直保持取消状态不会转变为其他状态;
 
     */
 
 static final int CANCELLED = 1;
 


 
 /**
 
     * 后继节点的线程处于等待状态,而当前节点的线程如果释放了同步状态或者被取消,将会通知后继节点,使后继节点的线程得以运行
 
     */
 
 static final int SIGNAL    = -1;
 


 
 /**
 
     * 节点在等待队列中,节点线程等待在Condition上,当其他线程对Condition调用了signal()后,改节点将会从等待队列中转移到同步队列中,加入到同步状态的获取中
 
     */
 
 static final int CONDITION = -2;
 


 
 /**
 
     * 表示下一次共享式同步状态获取将会无条件地传播下去
 
     */
 
 static final int PROPAGATE = -3;
 


 
 /** 等待状态 */
 
 volatile int waitStatus;
 


 
 /** 前驱节点 */
 
 volatile Node prev;
 


 
 /** 后继节点 */
 
 volatile Node next;
 


 
 /** 获取同步状态的线程 */
 
 volatile Thread thread;
 


 
 Node nextWaiter;
 


 
 final boolean isShared() {
 
 return nextWaiter == SHARED;
 
 }
 


 
 final Node predecessor() throws NullPointerException {
 
 Node p = prev;
 
 if (p == null)
 
 throw new NullPointerException();
 
 else
 
 return p;
 
 }
 


 
 Node() {
 
 }
 


 
 Node(Thread thread, Node mode) {
 
 this.nextWaiter = mode;
 
 this.thread = thread;
 
 }
 


 
 Node(Thread thread, int waitStatus) {
 
 this.waitStatus = waitStatus;
 
 this.thread = thread;
 
 }
 
}
 

CLH同步队列结构图如下:

入列

学了数据结构的我们,CLH队列入列是再简单不过了,无非就是tail指向新节点、新节点的prev指向当前最后的节点,当前最后一个节点的next指向当前节点。代码我们可以看看addWaiter(Node node)方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 private Node addWaiter(Node mode) {
 
 //新建Node
 
 Node node = new Node(Thread.currentThread(), mode);
 
 //快速尝试添加尾节点
 
 Node pred = tail;
 
 if (pred != null) {
 
            node.prev = pred;
 
 //CAS设置尾节点
 
 if (compareAndSetTail(pred, node)) {
 
                pred.next = node;
 
 return node;
 
 }
 
 }
 
 //多次尝试
 
        enq(node);
 
 return node;
 
 }
 

addWaiter(Node node)先通过快速尝试设置尾节点,如果失败,则调用enq(Node node)方法设置尾节点

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 private Node enq(final Node node) {
 
 //多次尝试,直到成功为止
 
 for (;;) {
 
 Node t = tail;
 
 //tail不存在,设置为首节点
 
 if (t == null) {
 
 if (compareAndSetHead(new Node()))
 
                    tail = head;
 
 } else {
 
 //设置为尾节点
 
                node.prev = t;
 
 if (compareAndSetTail(t, node)) {
 
                    t.next = node;
 
 return t;
 
 }
 
 }
 
 }
 
 }
 

在上面代码中,两个方法都是通过一个CAS方法compareAndSetTail(Node expect, Node update)来设置尾节点,该方法可以确保节点是线程安全添加的。在enq(Node node)方法中,AQS通过“死循环”的方式来保证节点可以正确添加,只有成功添加后,当前线程才会从该方法返回,否则会一直执行下去。

过程图如下:

出列

CLH同步队列遵循FIFO,首节点的线程释放同步状态后,将会唤醒它的后继节点(next),而后继节点将会在获取同步状态成功时将自己设置为首节点,这个过程非常简单,head执行该节点并断开原首节点的next和当前节点的prev即可,注意在这个过程是不需要使用CAS来保证的,因为只有一个线程能够成功获取到同步状态。过程图如下:

代码示例

本文示例读者可以通过查看下面仓库的中的 alibaba/java/ParentClass.java :

  • Github:https://github.com/JeffLi1993/java-core-learning-example
  • Gitee:https://gitee.com/jeff1993/java-core-learning-example

如果您对这些感兴趣,欢迎 star、follow、收藏、转发给予支持!

参考资料

  1. Doug Lea:《Java并发编程实战》
  2. 方腾飞:《Java并发编程的艺术》
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-04-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员泥瓦匠 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
AQS解析与实战
前段时间在面试,发现面试官都有问到同步器AQS的相关问题。AQS为Java中几乎所有的锁和同步器提供一个基础框架,派生出如ReentrantLock、Semaphore、CountDownLatch等AQS全家桶。本文基于AQS原理的几个核心点,谈谈对AbstractQueuedSynchronizer的理解,并实现一个自定义同步器。
捡田螺的小男孩
2020/04/15
6640
AQS解析与实战
【死磕Java并发】—– J.U.C之AQS:CLH同步队列
此篇博客所有源码均来自JDK 1.8 在上篇博客【死磕Java并发】-----J.U.C之AQS:AQS简介中提到了AQS内部维护着一个FIFO队列,该队列就是CLH同步队列。 CLH同步队列是一个FIFO双向队列,AQS依赖它来完成同步状态的管理,当前线程如果获取同步状态失败时,AQS则会将当前线程已经等待状态等信息构造成一个节点(Node)并将其加入到CLH同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点唤醒(公平锁),使其再次尝试获取同步状态。 在CLH同步队列中,一个节点表示一个线程,它保
芋道源码
2018/03/02
9990
【死磕Java并发】—– J.U.C之AQS:CLH同步队列
面试官:从源码角度讲讲ReentrantLock及队列同步器(AQS)
JDK 中独占锁(排他锁)的实现除了使用关键字 synchronized 外,还可以使用ReentrantLock。虽然在性能上 ReentrantLock 和 synchronized 没有什么大区别,但 ReentrantLock 相比 synchronized 而言功能更加丰富,使用起来更为灵活,也更适合复杂的并发场景。
业余草
2021/12/06
3530
面试官:从源码角度讲讲ReentrantLock及队列同步器(AQS)
多线程进阶——JUC并发编程之抽象同步队列AQS框架设计理念一探究竟🔥
现在看来我们还有点懵逼,这个框架具体是怎么设计的?下面我们翻看源码注释一探究竟!其中AQS里面维护了一个Node节点构造的CLH队列(FIFO)先进先出队列。
须臾之余
2021/12/28
3600
多线程进阶——JUC并发编程之抽象同步队列AQS框架设计理念一探究竟🔥
JUC同步器框架AbstractQueuedSynchronizer源码图文分析
Doug Lea大神在编写JUC(java.util.concurrent)包的时候引入了java.util.concurrent.locks.AbstractQueuedSynchronizer,Abstract Queued Synchronizer,也就是"基于队列实现的抽象同步器",一般我们称之为AQS。其实Doug Lea大神编写AQS是有严谨的理论基础的,他的个人博客上有一篇论文《The java.util.concurrent Synchronizer Framework》,文章在http://ifeve.com上可以找到相关的译文(《JUC同步器框架》),如果想要深入研究AQS必须要理解一下该论文的内容,然后详细分析一下AQS的源码实现。本文在阅读AQS源码的时候选用的JDK版本是JDK11。
Throwable
2020/06/23
1.2K0
AQS 锁核心类详解
AQS(AbstractQuenedSynchronizer 抽象队列同步器) 是一个用来构建锁和同步器的框架,使用 AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLock,Semaphore,其他的诸如 ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于 AQS的。AQS是一种提供了原子式管理同步状态、阻塞和唤醒线程功能以及队列模型的简单框架。当然,我们自己也能利用 AQS非常轻松容易地构造出符合我们自己需求的同步器。AQS 框架如下:上图中有颜色的为Method,无颜色的为Attribution。
Java架构师必看
2021/04/23
7560
深入理解AQS--jdk层面管程实现【管程详解的补充】
  1.java.util.concurrent包中的大多数同步器实现都是围绕着共同的基础行为,比如等待队列、条件队列、独占获取、共享获取等,而这些行为的抽象就是基于AbstractQueuedSynchronizer(简称AQS)实现的,AQS是一个抽象同步框架,可以用来实现一个依赖状态的同步器。
忧愁的chafry
2022/10/30
2880
深入理解AQS--jdk层面管程实现【管程详解的补充】
【多线程系列】JUC 中的另一重要大杀器 AQS 抽象队列同步器
👋 你好,我是 Lorin 洛林,一位 Java 后端技术开发者!座右铭:Technology has the power to make the world a better place.
Lorin 洛林
2023/11/07
4140
【多线程系列】JUC 中的另一重要大杀器 AQS 抽象队列同步器
Java并发编程之AQS以及源码解析
AQS(AbstractQueuedSynchronizer)是 Doug Lea 大师创作的用来构建锁或者其他同步组件(信号量、事件等)的基础框架类。
Java技术债务
2022/09/26
6860
Java并发编程之AQS以及源码解析
万字超强图解:AQS 以及 ReentrantLock 应用
写在前面 祝大家儿童节快乐🎁,保持童心,这篇文章作为儿童节礼物🍰送给大家。进入源码阶段了,写了十几篇的 并发系列 知识铺垫终于要派上用场了。相信很多人已经忘了其中的一些理论知识,别担心,我会在源码环节带入相应的理论知识点帮助大家回忆,做到理论与实践相结合,另外这是超长图文,建议收藏 Java SDK 为什么要设计 Lock 曾几何时幻想过,如果 Java 并发控制只有 synchronized 多好,只有下面三种使用方式,简单方便 public class ThreeSync { private
二哥聊运营工具
2021/12/17
4700
万字超强图解:AQS 以及 ReentrantLock 应用
JUC的核心类AQS详解
转自 https://www.javadoop.com/post/AbstractQueuedSynchronizer#toc4
Java技术江湖
2019/09/25
5370
面试2万月薪必会知识:AQS
AQS,全称:AbstractQueuedSynchronizer,是JDK提供的一个同步框架,内部维护着FIFO双向队列,即CLH同步队列。
公众号 IT老哥
2020/09/16
4140
面试2万月薪必会知识:AQS
Java的AQS
AQS是AbstractQueuedSynchronizer的简称。AQS提供了一种实现阻塞锁和一系列依赖FIFO等待队列的同步器的框架,为一系列同步器依赖于一个单独的原子变量(state)的同步器提供了一个非常有用的基础。子类们必须定义改变state变量的protected方法,这些方法定义了state是如何被获取或释放的。鉴于此,本类中的其他方法执行所有的排队和阻塞机制。子类也可以维护其他的state变量,但是为了保证同步,必须原子地操作这些变量。
用户3467126
2019/07/16
6610
Java的AQS
通过一个生活中的案例场景,揭开并发包底层AQS的神秘面纱
当你在学习某一个技能的时候,是否曾有过这样的感觉,就是同一个技能点学完了之后,过了一段时间,如果你没有任何总结,或者是不经常回顾,遗忘的速度是非常之快的。
用户1516716
2020/02/13
5640
【Java源码】AQS 解析
AQS 即 AbstractQueuedSynchronizer,是 java.util.concurrent.locks 包的一个重要概念。Java 中锁实现/同步的几种方式:synchronized,ReentrantLock,CAS。其中,可重入锁 ReentrantLock 就是基于 AbstractQueuedSynchronizer(AQS)的。因此,理解 AQS 的实现原理,对 Java 锁理解非常重要。本篇将结合 JDK1.8 源码,对 AQS 进行分析。
程序员架构进阶
2021/04/07
4330
【Java源码】AQS 解析
图文并茂:AQS 是怎么运行的?
如果你想深入研究Java并发的话,那么AQS一定是绕不开的一块知识点,Java并发包很多的同步工具类底层都是基于AQS来实现的,比如我们工作中经常用的Lock工具ReentrantLock、栅栏CountDownLatch、信号量Semaphore等,而且关于AQS的知识点也是面试中经常考察的内容,所以,无论是为了更好的使用还是为了应付面试,深入学习AQS都很有必要。
Java技术栈
2020/12/08
4870
图文并茂:AQS 是怎么运行的?
一篇文章让你掌握 AQS 核心知识
感谢原作者,本文转载自 https://my.oschina.net/xiongying0214/blog/1944627
好好学java
2019/07/01
5560
一篇文章让你掌握 AQS 核心知识
Juc并发编程06——深入剖析队列同步器AQS源码
原来lock,unlock等核心方法都是通过sync来实现的。而sync其实是它的一个内部类。
半旧518
2022/10/26
2700
Juc并发编程06——深入剖析队列同步器AQS源码
由浅入深逐步讲解Java并发的半壁江山AQS
synchronized 关键字是JDK官方人员用C++代码写的,在JDK6以前是重量级锁。Java大牛 Doug Lea对 synchronized 在并发编程条件下的性能表现不满意就自己写了个JUC,以此来提升并发性能,本文要讲的就是JUC并发包下的AbstractQueuedSynchronizer。
sowhat1412
2020/12/28
5550
由浅入深逐步讲解Java并发的半壁江山AQS
Java并发之-队列同步器AQS
AQS是AbstractQueuedSynchronizer的简称,是用来构建锁或者其他同步组建的基础框架,它使用一个 int 类型的成员变量来表示同步状态,通过内置的FIFO(先进先出)队列来完成资源获取和排队的。
胖虎
2019/06/26
4500
Java并发之-队列同步器AQS
相关推荐
AQS解析与实战
更多 >
加入讨论
的问答专区 >
1高级后端开发工程师擅长3个领域
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
    本文部分代码块支持一键运行,欢迎体验
    本文部分代码块支持一键运行,欢迎体验