Java并发之-队列同步器AQS
前言
AQS是AbstractQueuedSynchronizer的简称,是用来构建锁或者其他同步组建的基础框架,它使用一个 int 类型的成员变量来表示同步状态,通过内置的FIFO(先进先出)队列来完成资源获取和排队的。
在前面我讲了很多JUC中的同步工具,例如CountDownLatch、ReentrantLock等。其实我们知道这些同步工具都是通过继承AQS来实现的,所以AQS是这些同步工具的父类。所谓,了解一个人就要了解他的身世,爱一个人就要接受他的过去……
参考文献
《Java并发编程艺术》
正文
同步器提供的模版方法基本分为3类:独占式获取与释放同步状态、共享式获取与释放同步状态和查询同步队列中的等待情况。
本文以独享式获取与释放同步状态为主,让大家了解获取与释放的流程。
同步器接口示例
同步器提供3个方法来访问或修改同步状态。
- getState():获取当前同步状态。
- setState(int newState):设置当前同步状态。
- compareAndSetState(int expect,int update):使用CAS设置当前状态,可以保证原子性。
方法名称 | 描述 |
protected boolean tryAcquire(int arg) | 独占式获取同步状态,实现该方法需要查询当前状态并判断同步状态是否符合预期,然后再进行CAS设置同步状态。 |
protected boolean tryRelease(int arg) | 独占式释放同步状态,等待获取同步状态的线程将有机会获取同步状态 |
protected boolean tryAcquireShared(int arg) | 共享式获取同步状态,返回大于等于0表示获取成功,反之失败。 |
protected boolean tryReleaseShared(int arg) | 共享式释放同步状态 |
protected boolean isHeldExclusively(int arg) | 当前同步器是否在独占模式下被线程一直占用,该方法返回是否被当前线程占用,true为持有占用 |
同步队列结构
同步器内部依赖一个同步队列,该队列遵循FIFO来完成同步状态的管理,当前线程获取同步失败时,同步器会把当前线程以及等待状态等信息构造成一个节点(Node)并加入同步队列尾部,同时阻塞当前线程,当同步状态释放时,把首节点线程唤醒,使其再次尝试获取同步状态。
节点是同步器的基础,同步器拥有首节点(head),和尾节点(tail)。如图是同步队列的结构。
节点加入到队列尾节点
一个线程获取到来同步状态(或锁),其他线程没有获取到,然后就被加入到队列的尾节点,这个过程要求必须线程安全,所以用CAS设置尾节点,只有设置成功后,当前节点才正式与之前尾节点建立关联。
首节点获取
首节点是获取同步状态成功的节点,也就是出列队的线程是首节点,当首节点释放后,将唤醒后继节点,后继节点将会在获取同步状态时成功将自己设置为首节点,然后等待下次被释放。
说明:设置首节点是通过获取同步状态的线程完成的,由于只有一个线程可以获取同步状态,所以设置首节点不用CAS来保证线程安全。
独占式同步状态获取
获取同步状态通过acquire(int arg),该方法失败会进入同步队列。
讲解代码:
该方法完成了同步状态的获取、节点构造、加入队列以及在同步队列中自旋等操作,主要逻辑是:首先使用 tryAcquire 方法安全的获取线程的同步状态,如果失败则通过 addWaiter 方法构造尾节点加入队列中,最后调用 acquireQueued 方法使得该节点无限循环的方式获取同步状态,获取不到则阻塞节点的线程,解除阻塞只有唤醒前驱节点或阻塞线程中断来实现。enq 方法中,通过无限循环来保证节点正确添加。
节点进入同步队列后,就进入一个自旋的过程,每个线程都在观察,当满足条件,获取到同步状态就会从自旋过程退出,否则一直自旋。
public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();
}private Node addWaiter(Node mode) {Node node = new Node(Thread.currentThread(), mode);// 快速尝试在尾部添加Node pred = tail;if (pred != null) {node.prev = pred;if (compareAndSetTail(pred, node)) {pred.next = node;return node;}}enq(node);return node;}private Node enq(final Node node) {for (;;) {Node t = tail;if (t == null) { // Must initializeif (compareAndSetHead(new Node()))tail = head;} else {node.prev = t;if (compareAndSetTail(t, node)) {t.next = node;return t;}}}}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);}}
自旋获取同步示意图
首节点成功获取同步状态后将会唤醒后继节点,后继节点线程被唤醒后需要检查自己前驱节点是否是头节点。
白话:你要我继承你的位置做老大,首先我看你是不是我老大。
可以看出节点和节点之间在循环检查的过程中基本不互相通信,只是简单的判断自己的前驱节点是不是头节点而已,这样做符合FIFO。
独占式同步状态获取流程图
独占式同步状态释放
独占式同步器释放同步状态使用 release 方法。
public final boolean release(int arg) {if (tryRelease(arg)) {Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h);return true;}return false;}
执行该方式时,会唤醒头节点的后继节点线程,在释放同步状态时,同步器调用 tryRelease 方法释放同步状态,然后唤醒头节点的后继节点。
共享式同步状态获取与释放
这里不再贴代码讲解,我只做简单的介绍。
共享式获取与独占式获取主要区别在于同一时间能否有多个线程同时获取同步状态。举个例子,文件读写时,既保证高效有保证不被脏读的方法就是,写操作对资源独占访问,读操作可以共享访问。所以大家更好理解为什么 ReentrantReadWriteLock 的读可以共享了。关于共享方法在文章前面我已经列出了共享式方法的介绍。
个人微信,加微信可拉入java技术交流群。
Java并发之-队列同步器AQS相关推荐
- Java中的队列同步器AQS
一.AQS概念 1.队列同步器是用来构建锁或者其他同步组件的基础框架,使用一个int型变量代表同步状态,通过内置的队列来完成线程的排队工作. 2.下面是JDK8文档中对于AQS的部分介绍 public ...
- java同步队列_Java 中队列同步器 AQS(AbstractQueuedSynchronizer)实现原理
前言 在 Java 中通过锁来控制多个线程对共享资源的访问,使用 Java 编程语言开发的朋友都知道,可以通过 synchronized 关键字来实现锁的功能,它可以隐式的获取锁,也就是说我们使用该关 ...
- 并发——抽象队列同步器AQS的实现原理
一.前言 这段时间在研究Java并发相关的内容,一段时间下来算是小有收获了.ReentrantLock是Java并发中的重要部分,所以也是我的首要研究对象,在学习它的过程中,我发现它是基于抽象队列同步 ...
- 并发编程-15并发容器(J.U.C)核心 AbstractQueuedSynchronizer 抽象队列同步器AQS介绍
文章目录 J.U.C脑图 J.U.C核心AQS简介 AQS底层数据结构 AQS特点 J.U.C脑图 为了体现出AQS和线程池的重要性,上图单独将AQS和线程池拿出来了. J.U.C的构成如下: J.U ...
- 【学习笔记】抽象队列同步器AQS应用之BlockingQueue详解
文章目录 什么是AQS框架 Aqs核心源码 基于aqs实现的锁 BlockingQueue ArrayBlockingQueue LinkedBlockingQueue DelayQueue Bloc ...
- 05.抽象队列同步器AQS应用之Lock详解
AQS应用之Lock Java并发编程核心在于java.concurrent.util包而juc当中的大多数同步器实现都是围绕着共同的基础行为,比如等待队列.条件队列.独占获取.共享获取等,而这个行为 ...
- 六:抽象队列同步器AQS应用之BlockingQueue详解
目录 概要 队列类型 队列数据结构 ArrayBlockingQueue LinkedBlockingQueue DelayQueue BlockingQueue API 多线程生产者-消费者示例 并 ...
- Java 并发之 AQS 详解(上)
Java 并发之 AQS 详解 前言 Java SDK 为什么要设计 Lock 死锁问题 synchronized 的局限性 显式锁 Lock Lock 使用范式 Lock 是怎样起到锁的作用呢? 队 ...
- Java并发之AQS详解(文章里包含了两片文章结合着看后边文章不清楚,请看原文)
AQS全称抽象队列同步器(AbstractQuenedSynchronizer),它是一个可以用来实现线程同步的基础框架.当然,它不是我们理解的Spring这种框架,它是一个类,类名就是A ...
- JDK源码系列:AQS(队列同步器)原理
大家好,好久不见,今天看下JDK中的JUC包中AQS(AbstractQueuedSynchronizer 队列同步器)的实现原理. JUCL下的锁和synchronized提供的锁的区别 1.锁的获 ...
最新文章
- 使用Poco实现插件方式加载动态库
- [蓝桥杯][2013年第四届真题]剪格子-dfs
- 跳台阶游戏(洛谷P5613题题解,Java语言描述)
- 一看就会的 GitHub 骚操作,让你看上去像一位开源大佬
- 还要我带一个六级辅导班--痛苦!
- 力扣-204 计数质数
- ASP.net 2.0 的 Membership Provider 与 Role Provider 第一部分——引进资源
- rails mysql2 mac_用Mac osx10.6在Ruby on Rails上安裝mysql2 gem。
- Java数据结构:数组模拟的队列(Queue)和环形队列(Circle Queue)
- SCSA之信息安全概述
- linux中的代码比对工具meld
- c和java搞笑动图_拍一拍搞笑高清无水印表情包大全 拍一拍gif动图搞笑有趣表情包...
- 微信小程序服务器配置https站点
- 硬链接和符号链接详解
- SQLMAP的常用参数
- puppeteer开发中Evaluation failed: ReferenceError: __awaiter is not defined 报错处理
- js 忽略字母大小写
- 主动学习(Active Learning)简介综述汇总以及主流技术方案
- 环信开源计划开启即时通讯云开源平台时代
- ui平面设计好学吗?ui设计哪些工具是需要掌握的?