JDK源码系列:AQS(队列同步器)原理
大家好,好久不见,今天看下JDK中的JUC包中AQS(AbstractQueuedSynchronizer 队列同步器)的实现原理。
JUCL下的锁和synchronized提供的锁的区别
1、锁的获取和释放是显示的靠程序员用代码来控制的,增加了灵活性,可以实现更加复杂的应用场景
2、尝试非堵塞式的获取锁
3、可中断的获取锁
4、可超时的获取锁
5、等待队列可按条件分类(Condition),这样可以实现更加精确的按组唤醒操作
LOCK接口API
方法名称 |
描述 |
void lock() |
堵塞式的获取锁(不响应中断) |
void lockInterruptibly() |
堵塞式的获取锁(响应中断) |
boolean tryLock(); |
非堵塞式的获取锁(立刻响应,成功返回true,失败返回false) |
boolean tryLock(long time, TimeUnit unit) |
堵塞式的带超时时间的可响应中断的获取锁 |
void unlock(); |
释放当前线程持有的锁 |
Condition newCondition(); |
返回一个绑定到了当前lock实例上的新的条件对象,这是用来做队列分组的条件,每个条件对象都对应一个队列 |
AQS的地位
AQS = AbstractQueuedSynchronizer 队列同步器
AQS是JDK5.0 引入的一个抽象类,它对常见的lock场景进行了抽象,目的是对各种场景的lock提供基础支持,使锁实现起来更加容易(排队与唤醒功能)。
AQS位于java.util.concurrent.locks包中,可以看出它就是为lock服务的,ReentrantLock(独占式可重入锁)、Semaphore(共享式锁)、ReentrantReadWriteLock(混合式锁)、等都是基于它来实现的,还有CountDownLatch和ThreadPoolExecutor.Worker中也有AQS的影子。
AQS提供的能力
1、使用了一个int成员变量来表示同步状态
2、提供了一个FIFO队列来支持竞争线程的排队工作
3、通过模板方法设计模式来对外提供能力(子类覆盖某些步骤抽象方法)
4、定义三个方法来修改 同步状态值(getState()、setState(int newState)、compareAndSetState(int expect,int update))
5、支持独占式的获取同步状态,也支持共享式的获取同步状态
6、支持条件等待队列
AQS中方法
1、需要锁的实现类来实现的方法
注意下面的方法虽然被实现类重写,但是并不是为调用者准备的,它们的范围是protected,其实就是 模板方法 里面的一个步骤而已,不能直接对客户端开放,所以不是 public的。
方法名称 |
描述 |
protected boolean tryAcquire(int arg) |
独占式获取同步状态,通过getState查询当前状态,定义自己的逻辑,通过compareAndSetState 设置新的状态,true表示加锁成功 |
protected boolean tryRelease(int arg) |
独占式释放同步状态,可以安全的使用getState和setState方法 |
protected int tryAcquireShared(int arg) |
共享式获取同步状态,返回>=0的值表示加锁成功,<0 的值 表示加锁失败 |
protected boolean tryReleaseShared(int arg) |
共享式释放同步状态(需要CAS确保安全) |
protected boolean isHeldExclusively() |
是否在独占模式下被线程占用,一般用来判断是否被当前线程独占 |
2、客户端可以直接调用的API(可以叫模板方法)
Lock接口方法 |
AQS中的模板方法(独占式) |
AQS中的模板方法(共享式) |
public void lock() |
public final void acquire(int arg) |
public final void acquireShared(int arg) |
public void lockInterruptibly() |
public final void acquireInterruptibly(int arg) |
public final void acquireSharedInterruptibly(int arg) |
public boolean tryLock() |
尝试获取锁,不堵塞(不涉及线程排队,所以并不需要AQS提供模板方法),自己用AQS提供的那三个方法改变state状态即可,如果失败直接返回false |
和独占式同理,子类自己实现即可 |
public boolean tryLock(long timeout, TimeUnit unit) |
public final boolean tryAcquireNanos(int arg, long nanosTimeout),涉及到等待了,所以需要排队,这是和上面那个方法的区别 |
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) |
public void unlock() |
public final boolean release(int arg) |
public final boolean releaseShared(int arg) |
public Condition newCondition() |
返回一个ConditionObject 对象 |
不支持,也就是Condition对象只在独占式锁里才支持 |
通过对API的分析可以知道,AQS主要提供了:
1、独占式的获取与释放同步状态
2、共享式的获取与释放同步状态
3、查询同步队列中的线程排队情况
独占式锁:同一时刻只能由一个线程获取到,其它线程只能进入队列排队,等待。
共享式锁:同一时刻可以由多个线程持有。
混合式锁:当有读锁被其它线程持有期间,写锁线程只能堵塞等待前面所有的读锁释放,写锁堵塞之后进来的读锁只能排队到写锁线程后面,当读锁释放完毕后写锁获取成功,写锁释放完毕后,排在写锁后面的读锁可以同时获取到读锁。
AQS中内部类Node
这是AQS中用到的队列节点的定义,比较复杂,有多种模式和状态,这是因为要用它来支撑多种需求场景
节点的状态用一个int变量来表示,0表示初始值,负值(<0)表示结点处于有效等待状态,而正值(>0)表示结点已被取消
static final class Node {//共享节点static final Node SHARED = new Node();//独占节点static final Node EXCLUSIVE = null;//表示当前结点已取消调度。当timeout或被中断(响应中断的情况下),会触发变更为此状态,进入该状态后的结点将不会再变化。static final int CANCELLED = 1;//表示后继结点在等待当前结点唤醒。后继结点入队时,会将前继结点的状态更新为SIGNAL。static final int SIGNAL = -1;//表示结点等待在Condition上,当其他线程调用了Condition的signal()或者signalAll()方法后,//CONDITION状态的一个结点或者所有节点将从“条件等待队列”转移到“同步队列”中,等待获取同步锁。static final int CONDITION = -2;//共享模式下,前继结点不仅会唤醒其后继结点,同时也可能会唤醒后继的后继结点。//读写锁模式下,两个写锁之间的所有读锁都会被唤醒,直到遇到下一个写锁或者到尾结点static final int PROPAGATE = -3;//节点的状态,可以为 0,CANCELLED,SIGNAL,CONDITION,PROPAGATEvolatile int waitStatus;//前驱节点volatile Node prev;//后继节点volatile Node next;//当前节点绑定的线程信息volatile Thread thread;//Node既可以作为同步队列节点使用,也可以作为Condition的等待队列节点使用。//在作为同步队列节点时,nextWaiter可能有两个值:EXCLUSIVE、SHARED标识当前节点是独占模式还是共享模式;//在作为等待队列节点使用时,nextWaiter保存后继节点。Node nextWaiter;final boolean isShared() {return nextWaiter == SHARED;}final Node predecessor() throws NullPointerException {Node p = prev;if (p == null)throw new NullPointerException();elsereturn p;}Node() { // Used to establish initial head or SHARED marker}Node(Thread thread, Node mode) { // Used by addWaiterthis.nextWaiter = mode;this.thread = thread;}Node(Thread thread, int waitStatus) { // Used by Conditionthis.waitStatus = waitStatus;this.thread = thread;}
}
AQS中内部类ConditionObject
条件对象,每次 lock.newCondition()都会产生一个 新的 ConditionObject对象,每个对象里都维护了 一个 等待队列,用来维护基于此condition的等待线程,这样可以做到队列分组,便于更精确的唤醒目标线程。
await 在等待的过程中涉及到中断的响应问题,interruptMode中断模式来记录中断时间,该变量有三个值:
0:代表整个过程中一直没有中断发生,退出await方法后不需要任何动作;
THROW_IE:表示退出await()方法时需要抛出InteruptedException,这种模式对应于中断发生在signal之前,这是一种正常中断。
REINTERRUPT:表示退出await()方法时只需要再自我中断以下, 这种模式对应于中断发生在signal之后获取到同步锁之前, 即中断来的太晚了。
public class ConditionObject implements Condition, java.io.Serializable {private static final long serialVersionUID = 1173984872572414699L;/** 指向条件队列的首节点 */private transient Node firstWaiter;/** 指向条件队列的尾结点 */private transient Node lastWaiter;public ConditionObject() { }// public methods//唤醒一个条件等待节点,转移到 同步等待队列中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);}//不可中断的入队条件等待队列public final void awaitUninterruptibly() {Node node = addConditionWaiter();int savedState = fullyRelease(node);boolean interrupted = false;while (!isOnSyncQueue(node)) {LockSupport.park(this);if (Thread.interrupted())interrupted = true;}if (acquireQueued(node, savedState) || interrupted)selfInterrupt();}//中断模式,如果中断发生在 条件等待队列 出队之前时,则需要抛出中断异常;//如果中断时间发生在 重新竞争独占锁的过程中,则执行reinterrupt(自己只设置下中断标志,剩下的交给上层代码处理)/** Mode meaning to reinterrupt on exit from wait */private static final int REINTERRUPT = 1;/** Mode meaning to throw InterruptedException on exit from wait */private static final int THROW_IE = -1;//进入条件等待,可中断public final void await() throws InterruptedException {}//进入带超时时间的条件队列,可中断public final long awaitNanos(long nanosTimeout)throws InterruptedException {}//带绝对截止时间的等待,可中断public final boolean awaitUntil(Date deadline)throws InterruptedException {}public final boolean await(long time, TimeUnit unit)throws InterruptedException {}}
AQS基于Unsafe实现CAS操作
//支持原子操作相关
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long stateOffset;
private static final long headOffset;
private static final long tailOffset;
private static final long waitStatusOffset;
private static final long nextOffset;static {try {//AQS对象中state成员偏移量,相对于对象起始位置stateOffset = unsafe.objectFieldOffset(AbstractQueuedSynchronizer.class.getDeclaredField("state"));//AQS对象中head成员偏移量 headOffset = unsafe.objectFieldOffset(AbstractQueuedSynchronizer.class.getDeclaredField("head"));//AQS对象中tail成员偏移量 tailOffset = unsafe.objectFieldOffset(AbstractQueuedSynchronizer.class.getDeclaredField("tail"));//Node对象中waitStatus成员偏移量 waitStatusOffset = unsafe.objectFieldOffset(Node.class.getDeclaredField("waitStatus"));//Node对象中next成员偏移量 nextOffset = unsafe.objectFieldOffset(Node.class.getDeclaredField("next"));} catch (Exception ex) { throw new Error(ex); }
}//为AQS对象修改head,只被enq方法使用
private final boolean compareAndSetHead(Node update) {return unsafe.compareAndSwapObject(this, headOffset, null, update);
}//为AQS对象修改tail,只被enq方法使用
private final boolean compareAndSetTail(Node expect, Node update) {return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}//为node对象修改waitStatus
private static final boolean compareAndSetWaitStatus(Node node,int expect,int update) {return unsafe.compareAndSwapInt(node, waitStatusOffset,expect, update);
}//为node对象修改next
private static final boolean compareAndSetNext(Node node,Node expect,Node update) {return unsafe.compareAndSwapObject(node, nextOffset, expect, update);
}
AQS中的字段信息
//指向等待队列的首节点,首节点状态不能是cancelled,只能被setHead方法修改
private transient volatile Node head;
//指向等待队列的尾结点,只能被enq方法修改
private transient volatile Node tail;
//同步状态
private volatile int state;
图解AQS
独占模式
共享模式
总结
1、AQS提供一个int变量作为 同步状态(锁的本质就是一个内存变量),提供了CAS的方式安全修改state变量;
2、AQS提供了排队能力,对没有获取到锁的线程提供一个FIFO队列,来管理这些线程,它通过Unsafe提供的CAS能力来实现多线程下无锁的入队和出队,达到一个高性能的目的;
3、AQS对于入队成功的线程采取 LockSupport.park(thread)提供的能力 使某个线程休眠,降低CPU消耗,在合适的时机再通过 LockSupport.unpark(thread)操作唤醒休眠的线程来继续运行;
4、AQS提供了条件队列的支持(仅支持独占模式),根据条件对象的不同来将等待的线程分类管理(多队列),这样的好处是可以更精细化的管理这些线程,避免不必要的唤醒,减少对CPU的消耗。
5、AQS对独占锁提供线程锁定来支持可重入的特性;
6、公平锁模式下如果前面有排队的就加入到队尾,按先后顺序获取锁;非公平锁模式下不管前面有没有排队的,都会先尝试获取锁,失败后再入队尾;
JDK源码系列:AQS(队列同步器)原理相关推荐
- Java多线程系列(十):源码剖析AQS的实现原理
在并发编程领域,AQS号称是并发同步组件的基石,很多并发同步组件都是基于AQS实现,所以想掌握好高并发编程,你需要掌握好AQS. 本篇主要通过对AQS的实现原理.数据模型.资源共享方式.获取锁的过程, ...
- JDK源码系列(2)-Object类
引言 我们都知道,在Java中,Object是所有类的超类,所有的类其实都是隐含继承自Object类的,所以extends Object默认是不用写的,当然你写了也不会错.所有的类都可以使用Objec ...
- HashSet源码分析:JDK源码系列
1.简介 继续分析源码,上一篇文章把HashMap的分析完毕.本文开始分析HashSet简单的介绍一下. HashSet是一个无重复元素集合,内部使用HashMap实现,所以HashMap的特征耶继承 ...
- 大白话讲解JDK源码系列:从头到尾再讲一遍ThreadLocal
引言 其实网上有很多关于ThreadLocal的文章了,有不少文章也已经写的非常好了.但是很多同学反应还有一些部分没有讲解的十分清楚,还是有一定的疑惑没有想的十分清楚.因此本文主要结合常见的一些疑问. ...
- JDK源码系列:Future是如何实现的?
大家好,我们在异步编程时向线程池提交(submit)一个任务后会得到一个 Future对象,通过 future.get() 方法可以堵塞等待结果的完成,例如: public static void m ...
- JDK源码系列:子线程如何继承父线程上通过ThreadLocal绑定的数据
上一篇中老吕介绍了ThreadLocal线程数据绑定的原理,今天聊聊父子线程之间如何继承ThreadLocal上维护的数据. 开发过程中异步执行任务有两种情况,第一种情况是 主线程 通过 new Th ...
- JDK源码系列:synchronized与wait、notify、notifyAll
大家好,今天聊一聊synchronized与obj.wait().obj.notify().obj.notifyAll() 之间的关系以及它们的实现原理. 我们今天采用边写demo边分析的方式来进行. ...
- JDK源码系列:ThreadLocal弱引用真的是过度设计吗?
在<码处高效:Java开发手册>这本书上详细描述了ThreadLocal的原理,也有过度设计的说法, 难道弱引用设计真的没必要吗?对此老吕要仔细分析分析,ThreadLocal到底该不该使 ...
- JDK源码系列(3)-String
在JDK中,String的使用频率和被研究的程度都非常高,所以接下来我只说一些比较重要的内容. 一.String类的概述 String类的声明如下: public final class String ...
最新文章
- 京东到家基于netty与websocket的实践
- PDGAN: A Novel Poisoning Defense Method in Federated Learning Using Generative Adversarial Network笔记
- [BZOJ2017][Usaco2009 Nov]硬币游戏
- Python测试开发django1.简介
- 编译Nginx提示gzip module requires the zlib library
- [cb]SceneView 获取鼠标位置
- 使用 Python 和 Flask 实现 RESTful services
- 企业为什么要做高端网站优化呢?
- dede产生.php,怎么加快织梦dedeCMS内容生成速度
- 找个网页游戏插件开发团队
- 朗文当代高级英语辞典android,朗文当代高级英语辞典
- 深度学习与计算机视觉教程(18) | 深度强化学习 (梯度策略,Actor-Critic,DDPG,A3C)(CV通关指南·完结)
- win10设置linux虚拟网卡,win10系统安装虚拟网卡的详细步骤
- el-scrollbar 优化滚动条样式
- python编程 个人所得税计算器
- Dynamic Head: Unifying Object Detection Heads with Attentions论文阅读
- 软件测试常见英文单词汇总
- Fater-Rcnn原理详解
- js a-z索引排序
- mysql 军规_在互联网大厂必须遵守的MySql开发军规