上一篇: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. Error:Connection activation failed: No suitable device found for this connection 问题最新解决方案

    虽然网上有很多关于这个问题的解决方案,但是我还是决定自己再次重复写一下这个解决的方案,重在更新知识和了解VMware workstation 15新功能. 在使用VMware workstation克 ...

  2. 公有组件ShowCodeList实现原理之一一下拉框的实现

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  3. loadrunner 利用JDBC操作mysql数据库

    import lrapi.lr;import java.util.ArrayList;import java.util.List; import java.sql.Connection; import ...

  4. 远程访问Jupyter Notebook的两种方式:命令行和配置文件

    远程访问Jupyter Notebook的两种方式:命令行和配置文件 相关配置:Ubuntu 16.04服务器,本地Win10,使用了Xshell,Xftp工具. 相关配置主要分为三步: 服务器上的J ...

  5. Mybatis学习笔记-配置解析

    核心配置文件 mybatis-config.xml properties(属性) settings(设置) typeAliases(类型别名) typeHandlers(类型处理器) objectFa ...

  6. Linux下MySQL多实例部署记录

    什么是MySQL多实例 简单地说,Mysql多实例就是在一台服务器上同时开启多个不同的服务端口(3306.3307),同时运行多个Mysql服务进程,这些服务进程通过不同的socket监听不同的服务端 ...

  7. awk-05-数组

    数组 数组: 存储一系列相同类型的元素,键/值方式存储,通过下标(键)来访问值 awk 中数组称为关联数组,不仅可以使用数字作为下标,还可以使用字符串作为下标 数组元素的键和值存储在 awk 程序内部 ...

  8. DVWA(六):XSS-Reflected 反射型XSS全等级详解

    XSS 概念: 由于web应用程序对用户的输入过滤不严,通过html注入篡改网页,插入恶意脚本,从而在用户浏览网页时,控制用户浏览器的一种攻击. XSS类型: Reflected(反射型):只是简单的 ...

  9. 生成树-RSTP基础配置

    实验内容: 一.实验拓扑: 二.实验编址: 三.实验步骤: 1. 基本IP配置 2.启动设备 3.测试连通性 4.配置RSTP基本功能: 华为交换机默认开启了MSTP生成树模式,所以我们修改生成树模式 ...

  10. S3C2440—10.代码重定位

    文章目录 一.启动方式 1.1 NAND FLASH 启动 1.2 NOR FLASH 启动 二. 段的概念 2.1 重定位数据段 2.2 加载地址的引出 三.链接脚本 3.1 链接脚本的引入 3.2 ...