JDK源码之AQS源码剖析
除特别注明外,本站所有文章均为原创,转载请注明地址
AbstractQueuedSynchronizer(AQS)是JDK中实现并发编程的核心,平时我们工作中经常用到的ReentrantLock,CountDownLatch等都是基于它来实现的。
AQS类中维护了一个双向链表(FIFO队列), 如下图所示:
队列中的每个元素都用一个Node表示,我们可以看到,Node类中有几个静态常量表示的状态:
static final class Node {
/** Marker to indicate a node is waiting in shared mode */
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
static final Node EXCLUSIVE = null; /** waitStatus value to indicate thread has cancelled */
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
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;
}
}
此外,AQS中通过一个state的volatile变量表示同步状态。
那么AQS是如何通过队列实现锁操作的呢?
一.获取锁操作
下面的是AQS中执行获取锁的代码:
public final void acquire(int arg) {
/**通过tryAcquire获取锁,如果成功获取到锁直接终止(selfInterrupt),否则将当前线程插入队列
* 这里的Node.EXCLUSIVE表示创建一个独占模式的节点
*/
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
然而实际上,AQS中并没有实现上面的tryAcquire(arg)方法,具体获取锁的操作需要由其子类比如ReentrantLock中的Sync实现:
protected final boolean tryAcquire(int acquires) {
//取到当前线程
final Thread current = Thread.currentThread();
//获取到state值(前文提到)
int c = getState();
//state为0标识当前没有线程占有锁
//如果队列中前面没有元素(因为是公平锁的原因,非公平锁中不进行判断,如果state为0直接获取到锁),CAS修改当前值
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
//标识当前线程成功获取锁
setExclusiveOwnerThread(current);
return true;
}
}
//state不为0,且占有锁的线程是当前线程(这里涉及到一个可重入锁的概念)
else if (current == getExclusiveOwnerThread()) {
//增加重入次数
int nextc = c + acquires;
//如果次数值溢出,抛出异常
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
//如果锁已经被其它线程占用,获取锁失败
return false;
}
上面的代码注释中提到了可重入锁的概念,可重入锁又叫递归锁,简单来讲就是已经获取到锁的线程还可以再次获取到同一个锁,我们通常使用的syschronized操作,ReentrantLock都属于可重入锁。自旋锁则不属于可重入锁。
下面我们再看一下如果tryAcquire失败,AQS是如何处理的:
private Node addWaiter(Node mode) {
//创建一个队列的Node
Node node = new Node(Thread.currentThread(), mode);
//获取当前队列尾部
Node pred = tail;
if (pred != null) {
//CAS操作尝试插入Node到等待队列,这里只尝试一次
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//如果添加失败,enq这里会做自旋操作,知道插入成功。
enq(node);
return node;
}
//自旋操作添加元素到队列尾部
private Node enq(final Node node) {
for (;;) {
//获取尾节点
Node t = tail;
//如果尾节点为空,说明当前队列是空,需要初始化队列
if (t == null) {
//初始化当前队列
if (compareAndSetHead(new Node()))
tail = head;
} else {
//通过CAS操作插入Node,设置Node为队列的尾节点,并返回Node
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
/**
* 如果插入的节点前面是head,尝试获取锁,
*/
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
//自旋操作
for (;;) {
//获取当前插入节点的前置节点
final Node p = node.predecessor();
//前置节点是head,尝试获取锁
if (p == head && tryAcquire(arg)) {
//设置head为当前节点,表示获取锁成功
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//是否挂起当前线程,如果是,则挂起线程
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
上面的代码有些复杂,这里解释一下,之前的addWaiter代码已经将node加入了等待队列,所以这里需要让节点队列中挂起,等待唤醒。队列的head节点代表的是当前占有锁的节点,首先判断插入的node的前置节点是否是head,如果是,尝试获取锁(tryAcquire),如果获取成功则将head设置为当前节点;如果获取失败需要判断是否挂起当前线程。
/**
* 判断是否可以挂起当前线程
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//ws为node前置节点的状态
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
//如果前置节点状态为SIGNAL,当前节点可以挂起
return true;
if (ws > 0) { //通过循环跳过所有的CANCELLED节点,找到一个正常的节点,将当前节点排在它后面 //GC会将这些CANCELLED节点回收
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//将前置节点的状态修改为SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
//通过LockSupport挂起线程,等待唤醒
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
二.释放锁操作
有了获取锁的基础,再来看释放锁的源码就比较容易了,下面的代码执行的是AQS中释放锁的操作:
//释放锁的操作
public final boolean release(int arg)
//尝试释放锁,这里tryRelease同样由子类实现,如果失败直接返回false
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
下面的代码是尝试释放锁的操作:
protected final boolean tryRelease(int releases) {
//获取state值,释放一定值
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//如果差是0,表示锁已经完全释放
if (c == 0) {
free = true;
//下面设置为null表示当前没有线程占用锁
setExclusiveOwnerThread(null);
}
//如果c不是0表示锁还没有完全释放,修改state值
setState(c);
return free;
}
释放锁后,还需要唤醒队列中的一个后继节点:
private void unparkSuccessor(Node node) {
//将当前节点的状态修改为0
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
//从队列里找出下一个需要唤醒的节点
//首先是直接后继
Node s = node.next;
//如果直接后继为空或者它的waitStatus大于0(已经放弃获取锁了),我们就遍历整个队列,
//获取第一个需要唤醒的节点
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
//将节点唤醒
LockSupport.unpark(s.thread);
}
JDK源码之AQS源码剖析的更多相关文章
- 深度分析ReentrantLock源码及AQS源码,从入门到入坟,建议先收藏!
一.ReentrantLock与AQS简介 在Java5.0之前,在协调对共享对象的访问时可以使用的机制只有synchronized和volatile.Java5.0增加了一种新的机制:Reentra ...
- 全网最详细的AbstractQueuedSynchronizer(AQS)源码剖析(一)AQS基础
AbstractQueuedSynchronizer(以下简称AQS)的内容确实有点多,博主考虑再三,还是决定把它拆成三期.原因有三,一是放入同一篇博客势必影响阅读体验,而是为了表达对这个伟大基础并发 ...
- 全网最详细的AbstractQueuedSynchronizer(AQS)源码剖析(二)资源的获取和释放
上期的<全网最详细的AbstractQueuedSynchronizer(AQS)源码剖析(一)AQS基础>中介绍了什么是AQS,以及AQS的基本结构.有了这些概念做铺垫之后,我们就可以正 ...
- ArrayDeque(JDK双端队列)源码深度剖析
ArrayDeque(JDK双端队列)源码深度剖析 前言 在本篇文章当中主要跟大家介绍JDK给我们提供的一种用数组实现的双端队列,在之前的文章LinkedList源码剖析当中我们已经介绍了一种双端队列 ...
- 硬核剖析Java锁底层AQS源码,深入理解底层架构设计
我们常见的并发锁ReentrantLock.CountDownLatch.Semaphore.CyclicBarrier都是基于AQS实现的,所以说不懂AQS实现原理的,就不能说了解Java锁. 上篇 ...
- ReentrantLock 与 AQS 源码分析
ReentrantLock 与 AQS 源码分析 1. 基本结构 重入锁 ReetrantLock,JDK 1.5新增的类,作用与synchronized关键字相当,但比synchronized ...
- AQS源码详细解读
AQS源码详细解读 目录 AQS源码详细解读 基础 CAS相关知识 通过标识位进行线程挂起的并发编程范式 MPSC队列的实现技巧 代码讲解 独占模式 独占模式下请求资源 独占模式下的释放资源 共享模式 ...
- AQS源码深入分析之共享模式-你知道为什么AQS中要有PROPAGATE这个状态吗?
本文基于JDK-8u261源码分析 本篇文章为AQS系列文的第二篇,前文请看:[传送门] 第一篇:AQS源码深入分析之独占模式-ReentrantLock锁特性详解 1 Semaphore概览 共享模 ...
- AQS源码一窥-JUC系列
AQS源码一窥 考虑到AQS的代码量较大,涉及信息量也较多,计划是先使用较常用的ReentrantLock使用代码对AQS源码进行一个分析,一窥内部实现,然后再全面分析完AQS,最后把以它为基础的同步 ...
随机推荐
- 【转】一个工具类(可以控制多层嵌套的movieClip)
好多人也应该遇到和我一样的问题,当设计师给了我们一个多层嵌套的movieClip时,我们在写代码时无法将movieClip完全停止掉,主要是基于好多movieClip有深层嵌套,主时间轴不在最上层导致 ...
- mysql视图 更新中的问题
mysql view 类型 mysql的视图有三种类型:merge.temptable.undefined.如果没有ALGORITHM子句,默认算法是UNDEFINED(未定义的). 算法会影响MyS ...
- ArcGIS API for JavaScript 4.2学习笔记[30] 点和线高程查询(第八章完结)
终于到最后一篇了,可喜可贺. 本例先说明了如何进行单点的高程差分析,然后说明了道路的起伏分析.前者很直观地比较了两个年份的高程数据之间的差值,体现山区的高程变化(有啥用啊?)后者,一条路上的起点终点起 ...
- JavaScript Array 技巧
filter():返回该函数会返回true的项组成的数组 ,,,,]; var result = num.filter(function(item,index,array){ ); }) consol ...
- web开发与IC卡读卡器
前段时间有个项目在客户端web下使用IC卡读卡器,试了很多种方案都觉得麻烦,最后在网上找了个现成的方案,采用了YW-605HA读卡器,厂家就不说了,免得说做广告.开发起来也挺简单. 他们将IC卡读卡器 ...
- 设计模式的征途—3.工厂方法(Factory Method)模式
上一篇的简单工厂模式虽然简单,但是存在一个很严重的问题:当系统中需要引入新产品时,由于静态工厂方法通过所传入参数的不同来创建不同的产品,这必定要修改工厂类的源代码,将违背开闭原则.如何实现新增新产品而 ...
- Java在Debug的时候,有些变量能无限展开(循环了)?
抛异常的时候,Java Debug 时,有些变量能无限展开,怎么做到的? 先来一个报错的例子: Exception in thread "main" java.lang.Stack ...
- 用Rvm安装Ruby,Rails运行环境及常见错误解决方法
一.安装Rvm 1.下载安装Rvm $ curl -L https://get.rvm.io | bash -s stable 此时可能出现错误:"gpg: 无法检查签名:找不到公钥&quo ...
- Android -- 带你从源码角度领悟Dagger2入门到放弃
1,以前的博客也写了两篇关于Dagger2,但是感觉自己使用的时候还是云里雾里的,更不谈各位来看博客的同学了,所以今天打算和大家再一次的入坑试试,最后一次了,保证最后一次了. 2,接入项目 在项目的G ...
- NSUserDefaults的使用,保存登录状态和设置的轻量本地化存储
NSDictionary* defaults = [[NSUserDefaults standardUserDefaults] dictionaryRepresentation]; if([[NSUs ...