上一篇: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 == 0tryAcquireShared返回有资源后,就会唤醒等待队列中的第一个线程往下执行。

// 设置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;
}

参考:

setHeadAndPropagate源码分析

Java并发之AQS原理解读(三)的更多相关文章

  1. Java并发之AQS原理解读(二)

    上一篇: Java并发之AQS原理解读(一) 前言 本文从源码角度分析AQS独占锁工作原理,并介绍ReentranLock如何应用. 独占锁工作原理 独占锁即每次只有一个线程可以获得同一个锁资源. 获 ...

  2. Java并发之AQS原理解读(一)

    前言 本文简要介绍AQS以及其中两个重要概念:state和Node. AQS 抽象队列同步器AQS是java.util.concurrent.locks包下比较核心的类之一,包括AbstractQue ...

  3. Java并发之AQS原理剖析

    概述: AbstractQueuedSynchronizer,可以称为抽象队列同步器. AQS有独占模式和共享模式两种: 独占模式: 公平锁: 非公平锁: 共享模式: 数据结构: 基本属性: /** ...

  4. 并发之AQS原理(三) 如何保证并发

    并发之AQS原理(三) 如何保证并发 1. 如何保证并发 AbstractQueuedSynchronizer 维护了一个state(代表了共享资源)和一个FIFO线程等待队列(多线程竞争资源被阻塞时 ...

  5. 并发之AQS原理(一) 原理介绍简单使用

    并发之AQS原理(一) 如果说每一个同步的工具各有各的强大,那么这个强大背后是一个相同的动力,它就是AQS. AQS是什么 AQS是指java.util.concurrent.locks包里的Abst ...

  6. 并发之AQS原理(二) CLH队列与Node解析

    并发之AQS原理(二) CLH队列与Node解析 1.CLH队列与Node节点 就像通常医院看病排队一样,医生一次能看的病人数量有限,那么超出医生看病速度之外的病人就要排队. 一条队列是队列中每一个人 ...

  7. 深入理解Java并发框架AQS系列(三):独占锁(Exclusive Lock)

    一.前言 优秀的源码就在那里 经过了前面两章的铺垫,终于要切入正题了,本章也是整个AQS的核心之一 从本章开始,我们要精读AQS源码,在欣赏它的同时也要学会质疑它.当然本文不会带着大家逐行过源码(会有 ...

  8. Java并发之AQS详解

    一.概述 谈到并发,不得不谈ReentrantLock:而谈到ReentrantLock,不得不谈AbstractQueuedSynchronizer(AQS)! 类如其名,抽象的队列式的同步器,AQ ...

  9. Java并发之AQS详解(转)

    一.概述 谈到并发,不得不谈ReentrantLock:而谈到ReentrantLock,不得不谈AbstractQueuedSynchronized(AQS)! 类如其名,抽象的队列式的同步器,AQ ...

随机推荐

  1. <textarea></textarea>标签的placeholder属性不生效问题

    <textarea></textarea>标签的placeholder属性不生效问题   1.在用到<textarea></textarea>标签时,设 ...

  2. PDMan使用

    场景: 这几天项目要完结交付,需要补很多文档.此时发现甲方要求提供数据库设计文档,尽管我觉得他们不会看,但是人家要求,还是补一下吧!时间紧迫,要赶出整个项目的数据库设计文档比较麻烦,每个两三天不行.于 ...

  3. 数据结构——图的深度优先遍历(邻接矩阵表示+java版本)

    ​1.深度优先遍历(DFS) 图的深度优先遍历本质上是一棵树的前序遍历(即先遍历自身,然后遍历其左子树,再遍历右子树),总之图的深度优先遍历是一个递归的过程. 如下图所示,左图是一个图,右图是图的深度 ...

  4. CF427B

    没人用ST表么?他比线段树快. 考虑先把ST表跑下来,然后循环一遍区间的起点,看一下这个区间的最大值,和 \(t\) 比较一下即可. 然后这题就做完了.ST表裸题. int f[2000010][21 ...

  5. 【LeetCode】155. 最小栈

    155. 最小栈 知识点:栈:单调 题目描述 设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈. push(x) -- 将元素 x 推入栈中. pop() -- 删 ...

  6. AspNetCore添加API限流

    最近发现有客户在大量的请求我们的接口,出于性能考虑遂添加了请求频率限制. 由于我们接口请求的是.Net Core写的API网关,所以可以直接添加一个中间件,中间件中使用请求的地址当key,通过配置中心 ...

  7. [TensorFlow2.0]-张量与常用函数

    本人人工智能初学者,现在在学习TensorFlow2.0,对一些学习内容做一下笔记.笔记中,有些内容理解可能较为肤浅.有偏差等,各位在阅读时如有发现问题,请评论或者邮箱(右侧边栏有邮箱地址)提醒. 若 ...

  8. javaScript学习关于节点

    节点的常用属性和方法: 个人理解,对于节点来说,他就是html里面的标签对象. 通过具体的元素节点调用: getElementsByTagName()方法,获取当前节点的指定标签名孩子节点 appen ...

  9. 跟我一起写 Makefile(十三)

    五.定义模式规则 你可以使用模式规则来定义一个隐含规则.一个模式规则就好像一个一般的规则,只是在规则中,目标的定义需要有"%"字符."%"的意思是表示一个或多个 ...

  10. SpringCloud升级之路2020.0.x版-14.UnderTow AccessLog 配置介绍

    本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford server: u ...