AQS源码分析总结
AQS是并发编程的一个最基本组件,是一个抽象同步器。
网上有很多详细介绍AQS的博文,在这里我就不仔细介绍了,主要写一些重要的内容。
AQS中重要的几个属性:
//同步队列的头节点
private transient volatile Node head;
//同步队列的尾节点
private transient volatile Node tail;
//同步状态
private volatile int state;
由于一个共享资源同一时间可以被一条线程持有,也可以被多个线程持有,因此AQS中存在两种模式,共享模式和独占模式。
- 共享模式是共享状态值state每次可以由多个线程持有,如
CountDownLatch和Semaphore。 - 独占模式是共享状态值state每次只能由一条线程持有,其他线程如果需要获取,则需要阻塞。如
ReentrantLock。
独占锁的获取
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
先尝试获取锁,获取失败会调用addWaiter将当前线程添加到同步队列,之后队列中的每个节点会调用acquireQueued()方法通过自旋的方式先再尝试获取一下锁,如果失败,将当前节点的前驱节点的状态设置为SIGNAL,并将该线程阻塞,并判断该线程是否被中断。如果被中断了,当前节点获取锁后进行中断操作。
这里用到了模版方法的设计模式,tryAcquire是一个抽象方法,具体实现需要到子类中去完成。
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
下面详细介绍获取独占锁失败后,添加到队列的过程,调用addWaiter()方法。
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;
}
先创建出一个节点
- 当前尾节点不为空,采用尾插法利用CAS机制将新创建的节点添加到尾部,设置为尾节点。如果添加失败,就执行enq()方法。
- 如果尾节点为空,调用enq()方法。
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
enq是一个自旋操作,
1) 如果尾节点为空,说明当前线程是第一个加入同步队列的,先用CAS操作新添一个头节点head,并将尾节点指向它。第二次循环会跳往另一个执行区域。
2) 利用CAS操作将该节点添加到尾部。
直到自旋添加成功,就结束循环。
入队成功后,就要为该节点开启自旋,尝试获得锁。
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 GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
先获取当前节点的先驱节点
1) 如果先驱接节点是头节点并且成功获取同步状态的时候,将当前节点设置为头节点,然后将之前的头节点的next指针设置为null并且pre指针也为null,即将前节点与队列断开,
2)如果获取失败,就调用shouldParkAfterFailedAcquire方法,主要作用是将该节点的前驱节点的状态设置为SIGNAL,表示线程阻塞。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
在shouldParkAfterFailedAcquire方法中,如果前驱节点的状态是SIGNAL,就直接返回true;如果状态值大于0,表明该前驱节点已经被取消,则将该前驱节点去除掉,向前移;其他情况,将该前驱节点设置为SIGNAL。如果添加失败,就返回false,因为是自旋,下一次再尝试。
由于acquireQueued方法是一个循环,在第二次执行到shouldParkAfterFailedAcquire方法时,由于0号节点的waitStatus已经为Node.SIGNAL了,所以shouldParkAfterFailedAcquire方法会返回true,然后继续执行parkAndCheckInterrupt方法,将该线程已经阻塞,并怕判断该线程是否中断。
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
独占锁的释放
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释放成功后,如果头节点head不为null,并且状态值不为0,就会对它的后继节点进行唤醒。
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
//头节点的后继节点
Node s = node.next;
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)
//后继节点不为null时唤醒该线程
LockSupport.unpark(s.thread);
}
如果头节点的后继节点是空或者它的状态值大于0,表明它是失效的,就要从尾节点节点向前查找,找到最后一个状态值小于等于0的节点,然后对该节点进行唤醒。
共享锁的获取与释放
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
共享锁可以同时被多个线程拥有,可以在初始设置的时候将state设置为大于0的值,每一个线程获取一次,就减1,当state大于等于0时,别的线程也能够拥有该锁,当小于0时,就不可以,在共享锁模式下,当前线程拿到锁后,会直接通知后继节点去拿锁,而不必等待锁被释放的时候再通知。 在锁释放的时候,支持多个线程释放同步线程同步状态。
参考文章:
深入理解AbstractQueuedSynchronizer(AQS)
java并发编程系列:牛逼的AQS(上)
AQS源码分析总结的更多相关文章
- ReentrantLock 与 AQS 源码分析
ReentrantLock 与 AQS 源码分析 1. 基本结构 重入锁 ReetrantLock,JDK 1.5新增的类,作用与synchronized关键字相当,但比synchronized ...
- 并发-AQS源码分析
AQS源码分析 参考: http://www.cnblogs.com/waterystone/p/4920797.html https://blog.csdn.net/fjse51/article/d ...
- AQS源码分析笔记
经过昨晚的培训.对AQS源码的理解有所加强,现在写个小笔记记录一下 同样,还是先写个测试代码,debug走一遍流程, 然后再总结一番即可. 测试代码 import java.util.concurre ...
- AQS源码分析看这一篇就够了
好了,我们来开始今天的内容,首先我们来看下AQS是什么,全称是 AbstractQueuedSynchronizer 翻译过来就是[抽象队列同步]对吧.通过名字我们也能看出这是个抽象类 而且里面定 ...
- JAVA AQS源码分析
转自: http://www.cnblogs.com/pfan8/p/5010526.html JAVA AQS的全称为(AbstractQueuedSynchronizer),用于JAVA多线程的 ...
- AbstractQueuedSynchronizer AQS源码分析
申明:jdk版本为1.8 AbstractQueuedSynchronizer是jdk中实现锁的一个抽象类,有排他和共享两种模式. 我们这里先看排他模式,共享模式后面结合java.util.concu ...
- AQS源码分析
AQS全程为AbstractQueuedSynchronizer,其定义了一套多线程访问共享资源的同步框架,大部分的同步类的实现都依赖于他,比如ReentrantLock,ReentrantReadW ...
- java中AQS源码分析
AQS内部采用CLH队列.CLH队列是由节点组成.内部的Node节点包含的状态有 static final int CANCELLED = 1; static final int SIGNAL ...
- ArrayList源码分析--jdk1.8
ArrayList概述 1. ArrayList是可以动态扩容和动态删除冗余容量的索引序列,基于数组实现的集合. 2. ArrayList支持随机访问.克隆.序列化,元素有序且可以重复. 3. ...
随机推荐
- Linux文件和目录的属性及权限总结
本文讲述的是文件或目录的属性及权限,比如索引节点inode.文件类型.文件权限及属主:还对setuid.setgid及粘贴位进行了相关的讲解.其中,对ln.chmod.chown.chgrp.umas ...
- Android头像更换之详细操作
Android开发之头像的更换(拍照,从手机照片中选择) 先说一下昨天未解决的问题:原因是自己在获取对象时,没有将新加的图片属性加到该对象里,导致一直爆空指针异常. 接下来分析一下头像更换的具体操作: ...
- 机器学习(ML)九之GRU、LSTM、深度神经网络、双向循环神经网络
门控循环单元(GRU) 循环神经网络中的梯度计算方法.当时间步数较大或者时间步较小时,循环神经网络的梯度较容易出现衰减或爆炸.虽然裁剪梯度可以应对梯度爆炸,但无法解决梯度衰减的问题.通常由于这个原因, ...
- Codeforces_451_B
http://codeforces.com/problemset/problem/451/B 取前后第一个不满足条件的位置,逆序,判断. #include<cstdio> #include ...
- Codeforces 1304E 1-Trees and Queries (树上距离+思维)(翻译向)
题意 给你一棵树,q个询问(x,y,a,b,k),每次问你如果在(x,y)加一条边,那么a到b能不能走k步,同一个点可以走多次 思路(翻译题解) 对于一条a到b的最短路径x,可以通过左右横跳的方法把他 ...
- 使用logstash结合logback收集微服务日志
因为公司开发环境没有装elk,所以每次查看各个微服务的日志只能使用如下命令 这样子访问日志是并不方便,于是想为每个微服务的日志都用logstash收集到一个文件out中,那以后只要输出这个文件则可查看 ...
- java代码之美(16) ---Java8 Optional
Java8 Optional 一句话介绍Optional类:使用JDK8的Optional类来防止NullPointerException(空指针异常)问题. 一.前言 在我们开放过程中,碰到的异常中 ...
- mysql ---- Host '' is not allowed to connect to this MySQL server
mysql>use mysql mysql>update user set host= '%' where user = 'root'; 此时如果提示报错,不用管,继续往下走 select ...
- k8s系列---StorageClass
介绍这个概念前,需要提前知道存储卷pv/pvc之类的概念. 之前的文章有关于EFK日志系统的介绍,里面的环境是测试环境,完全按照教程一步步的操作,甚至注释掉了持久化存储,当真正线上部署时,又抓虾,打开 ...
- Shell脚本 统计店中店导出数据
有一个数据文件 yue.csv 是这样的 #head yue.csv 日期,商家名称,要求在线数,当天在线数,要求在线时长,在线时长达标数, ……"2017-12-31&quo ...