深入理解AQS--jdk层面管程实现【管程详解的补充】
什么是AQS
1.java.util.concurrent包中的大多数同步器实现都是围绕着共同的基础行为,比如等待队列、条件队列、独占获取、共享获取等,而这些行为的抽象就是基于AbstractQueuedSynchronizer(简称AQS)实现的,AQS是一个抽象同步框架,可以用来实现一个依赖状态的同步器。
2.JDK中提供的大多数的同步器如Lock, Latch, Barrier等,都是基于AQS框架来实现的
【1】一般是通过一个内部类Sync继承 AQS
【2】将同步器所有调用都映射到Sync对应的方法
AQS具备的特性:
1.阻塞等待队列 , 2.共享/独占 , 3.公平/非公平 , 4.可重入 , 5.允许中断
AQS定义两种资源共享方式
1.Exclusive-独占,只有一个线程能执行,如ReentrantLock(详情可查看 深入理解ReentrantLock类锁)
2.Share-共享,多个线程可以同时执行,如Semaphore/CountDownLatch
AQS定义两种队列
1.同步等待队列【主要用于维护获取锁失败时入队的线程】
【1】AQS当中的同步等待队列也称CLH队列,CLH队列是Craig、Landin、Hagersten三人发明的一种基于双向链表数据结构的队列,是FIFO先进先出线程等待队列,Java中的CLH队列是原CLH队列的一个变种,线程由原自旋机制改为阻塞机制。
【2】AQS 依赖CLH同步队列来完成同步状态的管理:
1)当前线程如果获取同步状态失败时,AQS则会将当前线程已经等待状态等信息构造成一个节点(Node)并将其加入到CLH同步队列,同时会阻塞当前线程
2)当同步状态释放时,会把首节点唤醒(公平锁),使其再次尝试获取同步状态。
3)通过signal或signalAll将条件队列中的节点转移到同步队列。(由条件队列转化为同步队列)
【3】图示:

2.条件等待队列【调用await()的时候会释放锁,然后线程会加入到条件队列,调用signal()唤醒的时候会把条件队列中的线程节点移动到同步队列中,等待再次获得锁】
【1】AQS中条件队列是使用单向列表保存的,用nextWaiter来连接:
1)调用await方法阻塞线程;
2)当前线程存在于同步队列的头结点,调用await方法进行阻塞(从同步队列转化到条件队列)
3.AQS 定义了5个队列中节点状态:
1)值为0,初始化状态,表示当前节点在sync队列中,等待着获取锁。
2)CANCELLED,值为1,表示当前的线程被取消;
3)SIGNAL,值为-1,表示当前节点的后继节点包含的线程需要运行,也就是unpark;
4)CONDITION,值为-2,表示当前节点在等待condition,也就是在condition队列中;
5)PROPAGATE,值为-3,表示当前场景下后续的acquireShared能够得以执行;
源码详解(将源码拆分为三块,抽象同步器AbstractQueuedSynchronizer类,节点Node类,条件对象ConditionObject类)
AbstractQueuedSynchronizer类解析
1.属性值解析
//用链表来表示队列
private transient volatile Node head;
private transient volatile Node tail; private volatile int state; //可以表示锁的加锁状态【独占锁只为1,共享锁可以大于1】,又可以表示锁的重入次数,0为没有加锁
2.方法解析
//定义了主体的大体逻辑,如入队,如尝试加锁
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
} protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
} //模板方法的处理,如果子类没有实现,则子类中调用的话会报错
//提供给子类去实现的公平与非公平的逻辑
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
//释放锁的逻辑
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
Node类详解
1.代码展示
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;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
//值为0,初始化状态,表示当前节点在sync队列中,等待着获取锁。
//CANCELLED,值为1,表示当前的线程被取消;
//SIGNAL,值为-1,表示当前节点的后继节点包含的线程需要运行,也就是unpark;
//CONDITION,值为-2,表示当前节点在等待condition,也就是在condition队列中;
//PROPAGATE,值为-3,表示当前场景下后续的acquireShared能够得以执行;
volatile int waitStatus;
volatile Node prev;//前驱结点
volatile Node next;//后继结点
volatile Thread thread; //与节点绑定的线程
Node nextWaiter; // 存储condition队列中的后继节点
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) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
Condition接口详解
1.代码展示
//Condition用来替代synchronized锁的监视器的功能,而且更加灵活
//一个Condition实例需要与一个lock进行绑定
public interface Condition {
//调用此方法的线程将加入等待队列,阻塞直到被通知或者线程中断
void await() throws InterruptedException; //调用此方法的线程将加入等待队列,阻塞直到被通知(线程中断忽略)
void awaitUninterruptibly(); //调用此方法的线程将加入等待队列,阻塞直到被通知或者线程中断或等待超时
long awaitNanos(long nanosTimeout) throws InterruptedException; //调用此方法的线程将加入等待队列,阻塞直到被通知或者线程中断或等待超时
boolean await(long time, TimeUnit unit) throws InterruptedException; //调用此方法的线程将加入等待队列,阻塞直到被通知或者线程中断或超出指定日期
boolean awaitUntil(Date deadline) throws InterruptedException; //唤醒一个等待中的线程
void signal(); //唤醒所以等待中的线程
void signalAll();
}
2.发现说明
【1】在Condition中,用await()替换wait(),用signal()替换notify(),用signalAll()替换notifyAll(),传统线程的通信方式,Condition都可以实现,这里注意,Condition是被绑定到Lock上的,要创建一个Lock的Condition必须用newCondition()方法。Condition的强大之处在于,对于一个锁,我们可以为多个线程间建立不同的Condition。如果采用Object类中的wait(), notify(), notifyAll()实现的话,当写入数据之后需要唤醒读线程时,不可能通过notify()或notifyAll()明确的指定唤醒读线程,而只能通过notifyAll唤醒所有线程,但是notifyAll无法区分唤醒的线程是读线程,还是写线程。所以,通过Condition能够更加精细的控制多线程的休眠与唤醒。
【2】但,condition的使用必须依赖于lock对象,通过lock对象的newCondition()方法初始化一个condition对象。
ConditionObject类详解【Condition接口的实现类】
1.属性值解析
//由头尾两个节点指针形成的链表来达到队列的效果
private transient Node firstWaiter;
private transient Node lastWaiter;
2.方法解析
【1】核心await方法
public final void await() throws InterruptedException {
//如果线程中断,直接抛出异常
if (Thread.interrupted())
throw new InterruptedException();
//进入等待队列中
Node node = addConditionWaiter();
//释放当前线程持有的锁,并获取当前同步器状态
int savedState = fullyRelease(node);
int interruptMode = 0;
//如果不在同步队列中,那么直接阻塞当前线程;直到被唤醒时,加入到同步队列中
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//此时已经被唤醒,那么尝试获取锁
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
//如果节点中断取消,那么清除节点
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
//addConditionWaiter将一个节点添加到condition队列中。在入队时,判断当前尾节点是不是CONDITION。如果不是则判断当前尾节点已经被取消,将当前节点出队。那么也就是说在队列中的节点状态,要么是CONDITION,要么是CANCELLED
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
//方法的作用是移除取消的节点。方法本身只有在持有锁的时候会被调用。方法会遍历当前condition队列,将所有非Condition状态的节点移除。
private void unlinkCancelledWaiters() {
Node t = firstWaiter;
Node trail = null;
while (t != null) {
Node next = t.nextWaiter;
if (t.waitStatus != Node.CONDITION) {
t.nextWaiter = null;
if (trail == null)
firstWaiter = next;
else
trail.nextWaiter = next;
if (next == null)
lastWaiter = trail;
}
else
trail = t;
t = next;
}
}
【2】核心signal方法与signalAll方法
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
public final void signalAll() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignalAll(first);
}
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
private void doSignalAll(Node first) {
lastWaiter = firstWaiter = null;
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
transferForSignal(first);
first = next;
} while (first != null);
}
final boolean transferForSignal(Node node) {
//如果不能更改waitStatus,则表示该节点已被取消
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
- 主要用于维护获取锁失败时入队的线程
深入理解AQS--jdk层面管程实现【管程详解的补充】的更多相关文章
- Kotlin协程作用域与Job详解
Job详解: 在上一次https://www.cnblogs.com/webor2006/p/11725866.html中抛出了一个问题: 所以咱们将delay去掉,需要改造一下,先把主线程的dela ...
- 2018.10.7 理解Hibernate的工作原理及其中的ORM详解
复习 hibernate框架 简介j及其搭建: hibernate是一个开源框架,它是对象关联关系映射的框架,它对JDBC做了轻量级的封装,而我们java程序员可以使用面向对象的思想来操纵数据库. 1 ...
- libco协程库上下文切换原理详解
缘起 libco 协程库在单个线程中实现了多个协程的创建和切换.按照我们通常的编程思路,单个线程中的程序执行流程通常是顺序的,调用函数同样也是 “调用——返回”,每次都是从函数的入口处开始执行.而li ...
- JDK动态代理、CGLIB动态代理详解
Spring的AOP其就是通过动态代理的机制实现的,所以理解动态代理尤其重要. 动态代理比静态代理的好处: 1.一个动态代理类可以实现多个业务接口.静态代理的一个代理类只能对一个业务接口的实现类进行包 ...
- Java学习系列(一)Java的运行机制、JDK的安装配置及常用命令详解
俗话说:“十五的月亮十六圆”.那学习是不是也是如此呢?如果把月亮看成是我们的愿望,那十五便是我们所处的“高原期”,坚持迈过这个坎,我相信你的愿望终究会现实的.记得马云曾说:今天很残酷,明天更残酷,后天 ...
- Linux-进程描述符 task_struct 详解
为了描述控制进程的运行,系统中存放进程的管理和控制信息的数据结构称为进程控制块 PCB(Process Control Block),它是进程实体的一部分,是操作系统中最重要的记录性数据结构.它是进程 ...
- JDK 安装目录中 native2ascii.exe 命令详解
native2ascii 简介 native2ascii 是 sun java sdk提供的一个工具.用来将别的文本类文件(比如*.txt,*.ini,*.properties,*.java等等)编码 ...
- 你真的理解编码吗?unicode,utf8,utf16详解
背景 前两天在网上看到一篇关于编码的讨论,仔细学习了一下unicode,utf8,utf16的定义.这篇博客旨在让读者真正理解他们是什么. 什么是编码 在阅读本文之前建议读者先去阅读这篇文章:http ...
- JDK自带的keytool证书工具详解
一.生成证书 keytool -genkey -alias tomcat -keyalg RSA -keystore D:/tomcat.keystore -keypass 123456 -store ...
随机推荐
- ACWing94. 递归实现排列型枚举
题面 把 1∼n 这 n个整数排成一行后随机打乱顺序,输出所有可能的次序. 输入格式 一个整数 n. 输出格式 按照从小到大的顺序输出所有方案,每行 1 个. 首先,同一行相邻两个数用一个空格隔开. ...
- PostGresql listen与notify命令
LISTEN与NOTIFY命令 PostgreSQL提供了client端和其他client端通过服务器端进行消息通信的机制.这种机制 是通过LISTEN和NOTIFY命令来完成的. 1.LISTEN与 ...
- SpringBoot(一、快速入门)
1.SpringBoot简介 Spring Boot 是由 Pivotal 团队提供的全新框架,其设计目的是用来简化新 Spring 应用的初始搭建以及开发过程.该框架使用了特定的方式来进行配置, ...
- Linux 06 用户组管理
参考源 https://www.bilibili.com/video/BV187411y7hF?spm_id_from=333.999.0.0 版本 本文章基于 CentOS 7.6 概述 每个用户都 ...
- Docker 03 镜像命令
参考源 https://www.bilibili.com/video/BV1og4y1q7M4?spm_id_from=333.999.0.0 https://www.bilibili.com/vid ...
- virtio_net 设备的队列数问题
virtio_net设备的其他问题:见 https://www.cnblogs.com/10087622blog/p/15886345.html 一个virtio_net设备在 virtnet_pro ...
- C++ IO流_数据的旅行之路
1. 前言 程序中的数据总是在流动着,既然是流动就会有方向.数据从程序的外部流到程序内部,称为输入:数据从程序内部流到外部称为输出. C++提供有相应的API实现程序和外部数据之间的交互,统称这类AP ...
- OpenJudge 1.5.15 银行利息
15:银行利息 总时间限制: 1000ms 内存限制: 65536kB 描述 农夫约翰在去年赚了一大笔钱!他想要把这些钱用于投资,并对自己能得到多少收益感到好奇.已知投资的复合年利率为R(0到20之间 ...
- KingbaseES V8R6 维护管理案例之---Kstudio在CentOS 7启动故障
案例说明: 在CentOS 7上安装KingbaseES V8R6C006数据库后,启动Kstudio图形界面启动失败,gtk动态库加载失败,安装gtk相关动态库后,问题解决. 适用版本: Kin ...
- Trigger Before 与 After 区别
用户在使用trigger时,经常会面临before or after的选择问题.二者有什么区别?从字面理解,before trigger 是在触发操作完成之前完成,而after 是在触发操作完成之后完 ...