Java并发之AQS原理解读(三)
前言
本文从源码角度分析AQS共享锁工作原理,并介绍下使用共享锁的子类如何工作的。
共享锁工作原理
共享锁与独占锁的不同之处在于,获取锁和释放锁成功后,都会循环唤醒从表头开始的第一个阻塞结点,直到表头没有改变。
doReleaseShared方法存在无效的调用,即存在无效的线程唤醒,但为了避免程序出现问题,无伤大雅。PROPAGATE状态用于setHeadAndPropagate方法判断是否唤醒阻塞结点。
获取锁
1、先尝试 tryAcquireShared,如果获取锁成功直接返回;
2、否则,addWaiter先将线程封装成Node入队,再判断当前节点的前驱是否是head头结点,是的话尝试tryAcquireShared获取锁,如果锁资源>=0则将当前节点设置为head并循环唤醒从表头开始的第一个等待结点,直到表头节点没有改变;不是头结点的话,将当前节点的前驱的waitStatus设为SIGNAL,并阻塞当前节点。
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
private void doAcquireShared(int arg) {
// 结点入队
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
// 设置为头结点并唤醒后继结点
setHeadAndPropagate(node, r);
p.next = null; // help GC
// 当发生线程中断时,补偿设置中断标识
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
// 设置前驱结点等待状态 waitStatus 为 SIGNAL,并阻塞自己
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head;
setHead(node);
/**
* 当锁资源大于 0 时,或者旧头结点 waitStatus 为 PROPAGATE 或者新节点 waitStatus 为 PROPAGATE
* 并且新节点的下一节点是可共享的时,唤醒下一个结点
*
* h == null 和 (h = head) == null 一定不成立,因为之前调用了 addWaiter
*/
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}
private void doReleaseShared() {
for (;;) {
Node h = head;
// 当队列存储等待结点时
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
// 将过渡状态 0 设为可传播 PROPAGATE
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue;
}
// 如果头结点发生了改变,则继续唤醒下一个等待结点
if (h == head)
break;
}
}
释放锁
1、直接尝试释放锁,如果失败则返回;
2、如果释放锁成功,则循环唤醒从表头开始的第一个等待结点,直到表头节点没有改变。
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
private void doReleaseShared() {
for (;;) {
Node h = head;
// 当队列存储等待结点时
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
// 将过渡状态 0 设为可传播 PROPAGATE
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue;
}
// 如果头结点发生了改变,则继续唤醒下一个等待结点
if (h == head)
break;
}
}
CountDownLatch
将state设置为初始化倒数,每次countDown都递减state。如果state != 0时,tryAcquireShared都返回没有资源,线程执行到await就写入队列阻塞;直到某个线程执行完state == 0,tryAcquireShared返回有资源后,就会唤醒等待队列中的第一个线程往下执行。
// 设置state
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
Sync(int count) {
setState(count);
}
// 递减 state
public void countDown() {
sync.releaseShared(1);
}
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
/**
* 阻塞等待,直到 state == 0
* 因为当 state == 0 时,tryAcquireShared 才返回正数,表示有锁资源
* 从而 sync#acquireSharedInterruptibly 可以执行成功,不再阻塞
*/
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
Java并发之AQS原理解读(三)的更多相关文章
- Java并发之AQS原理解读(二)
上一篇: Java并发之AQS原理解读(一) 前言 本文从源码角度分析AQS独占锁工作原理,并介绍ReentranLock如何应用. 独占锁工作原理 独占锁即每次只有一个线程可以获得同一个锁资源. 获 ...
- Java并发之AQS原理解读(一)
前言 本文简要介绍AQS以及其中两个重要概念:state和Node. AQS 抽象队列同步器AQS是java.util.concurrent.locks包下比较核心的类之一,包括AbstractQue ...
- Java并发之AQS原理剖析
概述: AbstractQueuedSynchronizer,可以称为抽象队列同步器. AQS有独占模式和共享模式两种: 独占模式: 公平锁: 非公平锁: 共享模式: 数据结构: 基本属性: /** ...
- 并发之AQS原理(三) 如何保证并发
并发之AQS原理(三) 如何保证并发 1. 如何保证并发 AbstractQueuedSynchronizer 维护了一个state(代表了共享资源)和一个FIFO线程等待队列(多线程竞争资源被阻塞时 ...
- 并发之AQS原理(一) 原理介绍简单使用
并发之AQS原理(一) 如果说每一个同步的工具各有各的强大,那么这个强大背后是一个相同的动力,它就是AQS. AQS是什么 AQS是指java.util.concurrent.locks包里的Abst ...
- 并发之AQS原理(二) CLH队列与Node解析
并发之AQS原理(二) CLH队列与Node解析 1.CLH队列与Node节点 就像通常医院看病排队一样,医生一次能看的病人数量有限,那么超出医生看病速度之外的病人就要排队. 一条队列是队列中每一个人 ...
- 深入理解Java并发框架AQS系列(三):独占锁(Exclusive Lock)
一.前言 优秀的源码就在那里 经过了前面两章的铺垫,终于要切入正题了,本章也是整个AQS的核心之一 从本章开始,我们要精读AQS源码,在欣赏它的同时也要学会质疑它.当然本文不会带着大家逐行过源码(会有 ...
- Java并发之AQS详解
一.概述 谈到并发,不得不谈ReentrantLock:而谈到ReentrantLock,不得不谈AbstractQueuedSynchronizer(AQS)! 类如其名,抽象的队列式的同步器,AQ ...
- Java并发之AQS详解(转)
一.概述 谈到并发,不得不谈ReentrantLock:而谈到ReentrantLock,不得不谈AbstractQueuedSynchronized(AQS)! 类如其名,抽象的队列式的同步器,AQ ...
随机推荐
- POJ3190 - 优先队列 贪心
POJ3190 将所有牛从小到大排序然后用优先队列(小根堆)依次记录插入的牛的结束时间,如果插入牛时起始时间大于首元素,ans不增加并弹出首元素. 挺简单的.那么为什么我会写(水)这篇博客呢? #in ...
- python安装pyeda库--windows版
本页介绍了如何购买自己的PyEDA闪亮副本.PyEDA项目的主要目标是成为主流的Python软件包,并遵守社区中遵守的大多数约定. 支持平台 PyEDA支持Windows以及任何带有C编译器的平台.作 ...
- odoo里的开发案例
1.模块命名[驼峰命名方法] res开头的是:resources 常见模型:res.users, res.company, res.partner, res.config.setti ...
- THE MINTO PYRAMID PRINCIPLE
金字塔原理:(重点突出,逻辑清晰.层次分明,简单易懂的思考方式.沟通方式.规范的动作.) 结构:结论先行,以上统下,归类分组,逻辑递进.先重要后次要,先总结后具体,先框架后细节,先结论后原因,先结果后 ...
- QLabel的使用
现在学习一个简单的控件Label. 第一步:打开designer.exe.拖动一个控件到主窗口,双击可以编辑文字. 第二步:设置字体大小 第三步:设置文字颜色 第四步:设置背景色 第五步:将文字居中 ...
- 如何选择数据分析工具?BI工具需要具备哪些功能?
数据分析使企业能够分析其所有数据(实时,历史,非结构化,结构化,定性),以识别模式并生成洞察力,以告知并在某些情况下使决策自动化,将数据情报与行动联系起来.当今最好的数据分析工具解决方案支持从访问.准 ...
- VUE 之_this.da 和 this
作者:张艳涛 日期:2020-07-29 this用在第一层,如果在JS第3二层,要用 _this importfxx(obj) { let _this = this; let inputDOM = ...
- centos7 安装mariadb、"systemctl status mariadb.service" and "journalctl -xe" for details
centos7 mariadb 安装 也可解决此错误:ob for mariadb.service failed because the control process exited with err ...
- 记一次WindowsServer2012提权 - 烂土豆
此次主题:烂土豆 shell咋拿的 我也忘了 好像是添加友情链接那里还是啥 不重要了 直接获取shell后 先用systeminfo > 1.txt了一下 然后放到了 windows-explo ...
- Anaconda创建虚拟环境并使用Jupyter-notebook打开虚拟环境
由于需要使用Tensorflow的1.xx的低版本,但是cmd下载一直报错没有解决,就想着用Anaconda,但是真实环境我已经装上Tensorflow2.0了不想换了,所以就想着再建一个python ...