JUC系列回顾之-CountDownLatch底层原理和示例
CountDownLatch 是一个同步工具类,允许一个线程或者多个线程等待其他线程完成操作,再执行。
CountDownLatch(int count)
构造一个用给定计数初始化的 CountDownLatch。 // 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。
void await()
// 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。
boolean await(long timeout, TimeUnit unit)
// 递减锁存器的计数,如果计数到达零,则释放所有等待的线程。
void countDown()
// 返回当前计数。
long getCount()
// 返回标识此锁存器及其状态的字符串。
String toString()
CountDownLatch和CyclicBarrier的区别:
(1).CountDownLatch 的作用是允许1或者多个线程,等待另外N个线程完成某件事情之后,这1个或者多个线程才能执行。CyclicBarrier 是N个线程相互等待,任何一个线程完成任务之前,所有的线程必须等待。
(2).CountDownLatch 计数器是一次性的,无法被重置的,而CyclicBarrier的计数器在调用reset方法之后,还可以重新使用,因此被称为循环的barrier。
CountDownLatch 底层实现:
1.构造方法:创建一个Sync对象,而Sync继承AQS。
/**
* Constructs a {@code CountDownLatch} initialized with the given count.
*
* @param count the number of times {@link #countDown} must be invoked
* before threads can pass through {@link #await}
* @throws IllegalArgumentException if {@code count} is negative
*/
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
2.Sync 是CountDownLatch的内部私有类,组合到CountDownLatch里:
/**
* Synchronization control For CountDownLatch.
* Uses AQS state to represent count.
*/
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L; Sync(int count) {
setState(count);
} int getCount() {
return getState();
} protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
} protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
} private final Sync sync;
在AQS中state是一个private volatile int类型的对象。CountDownLatch使用state来计数,CountDownLatch的getCount最终调用的是AQS的getState()
,返回state进行计数。
3.await()方法:调用AQS的acquireSharedInterruptibly方法
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
//1.获取共享锁
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
//判断线程是否为中断状态,如果是抛出interruptedException
if (Thread.interrupted())
throw new InterruptedException();
//尝试获取共享锁,尝试成功就返回,否则调用doAcquireSharedInterruptibly方法
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
//2.尝试获取共享锁,重写AQS里面的方法
protected int tryAcquireShared(int acquires) {
//锁状态 == 0,表示所没有被任何线程所获取,即是可获取的状态,否则锁是不可获取的状态
return (getState() == 0) ? 1 : -1;
}
//3.doAcquireSharedInterruptibly方法会使得当前线程一直等待,直到当前线程获取到锁(或被中断)才返回
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
//创建“当前线程”的Node节点,且node中记录的锁是“共享锁”类型,并将节点添加到CLH队列末尾。
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
//获取前继节点,如果前继节点是等待锁队列的表头,则尝试获取共享锁
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
//前继节点不是表头,当前线程一直等待,直到获取到锁
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
/*说明:4.shouldParkAfterFailedAcquire 返回当前线程是否应该阻塞
(01) 关于waitStatus请参考下表(中扩号内为waitStatus的值),更多关于waitStatus的内容,可以参考前面的Node类的介绍。 CANCELLED[1] -- 当前线程已被取消
SIGNAL[-1] -- “当前线程的后继线程需要被unpark(唤醒)”。一般发生情况是:当前线程的后继线程处于阻塞状态,而当前线程被release或cancel掉,因此需要唤醒当前线程的后继线程。
CONDITION[-2] -- 当前线程(处在Condition休眠状态)在等待Condition唤醒
PROPAGATE[-3] -- (共享锁)其它线程获取到“共享锁”
[0] -- 当前线程不属于上面的任何一种状态。
(02) shouldParkAfterFailedAcquire()通过以下规则,判断“当前线程”是否需要被阻塞。 规则1:如果前继节点状态为SIGNAL,表明当前节点需要被unpark(唤醒),此时则返回true。
规则2:如果前继节点状态为CANCELLED(ws>0),说明前继节点已经被取消,则通过先前回溯找到一个有效(非CANCELLED状态)的节点,并返回false。
规则3:如果前继节点状态为非SIGNAL、非CANCELLED,则设置前继的状态为SIGNAL,并返回false。
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 前驱节点的状态
int ws = pred.waitStatus;
// 如果前驱节点是SIGNAL状态,则意味着当前线程需要unpark唤醒,此时返回true
if (ws == Node.SIGNAL) return true;
// 如果前继节点是取消的状态,则设置当前节点的“当前前继节点为”原节点的前继节点
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// waitStatus must be 0 or PROPAGATE. Indicate that we need a signal, but don't park yet. Caller will need to retry to make sure
//it cannot acquire before parking.
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
4. countDown()源码 :
//1.该方法其实调用AQS中的releaseShared(1)释放共享锁方法。
public void countDown() {
sync.releaseShared(1);
}
//2.目的是让当前线程释放它所持有的共享锁,它首先会通过tryReleaseShared()去尝试释放共享锁。尝试成功,则直接返回;尝试失败,则通过doReleaseShared()去释放共享锁。
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
//3.tryReleaseShared()在CountDownLatch.java中被重写,释放共享锁,将锁计数器-1
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
// 获取“锁计数器”的状态
int c = getState();
if (c == 0)
return false;
// “锁计数器”-1
int nextc = c-1;
// 通过CAS函数进行赋值。
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
实例:
public class CountDownLatchTest1 {
private static int SPORTSMAN_COUNT = 10;
private static final Random random = new Random();
// 用于判断发令之前运动员是否已经进入准备状态,需要等待10个运动员准备就绪,占有锁,等待10个运动员完成,释放锁。
private static CountDownLatch readyLatch = new CountDownLatch(SPORTSMAN_COUNT);
// 用于判断裁判是否已经发令,占有锁,等待裁判发令完成,释放锁
private static CountDownLatch startLatch = new CountDownLatch(1);
public static void main(String[] args) {
// 用于判断发令之前运动员是否已经进入准备状态,需要等待10个运动员准备就绪,占有锁,等待10个运动员完成,释放锁。
// CountDownLatch readyLatch = new CountDownLatch(SPORTSMAN_COUNT);
// 用于判断裁判是否已经发令,占有锁,等待裁判发令完成,释放锁
// CountDownLatch startLatch = new CountDownLatch(1);
// 启动10个线程,也就是10个运动员,做准备工作
for (int i = 0; i < SPORTSMAN_COUNT; i++) {
Thread t = new Thread(new MyTask((i + 1) + "号运动员", readyLatch, startLatch));
t.start();
}
// 当前运动员在其他运动员准备就绪前一直等待,也就是说等readyLatch倒数计数器为0之前一直等待
try {
readyLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 裁判发令,释放锁
startLatch.countDown();
System.out.println("裁判:所有运动员准备完毕,开始...");
}
static class MyTask implements Runnable {
private Lock lock = new ReentrantLock();
private CountDownLatch ready;
private CountDownLatch start;
private String name;
/**
*
* (构造方法)
*
* @param ready
* @param start
* @param name 运动员名称
*/
public MyTask(String name, CountDownLatch ready, CountDownLatch start) {
this.ready = ready;
this.start = start;
this.name = name;
}
@Override
public void run() {
lock.lock();
try {
// 1. 写运动员准备就绪的逻辑,准备readyTime秒
int readyTime = random.nextInt(1000);
System.out.println(name + ":我需要" + readyTime + "秒的时间准备。");
try {
Thread.sleep(readyTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + "我已经准备完毕!");
// 释放锁readyLatch-1,表示一个运动员已经就绪
ready.countDown();
try {
// 等待裁判发开始命令
start.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + ":开跑...");
} catch (Exception e) {
// TODO: handle exception
} finally {
lock.unlock();
}
}
}
}
运行结果:
1号运动员:我需要757秒的时间准备。
2号运动员:我需要9秒的时间准备。
3号运动员:我需要602秒的时间准备。
4号运动员:我需要232秒的时间准备。
5号运动员:我需要454秒的时间准备。
6号运动员:我需要440秒的时间准备。
7号运动员:我需要333秒的时间准备。
8号运动员:我需要406秒的时间准备。
9号运动员:我需要613秒的时间准备。
10号运动员:我需要121秒的时间准备。
2号运动员我已经准备完毕!
10号运动员我已经准备完毕!
4号运动员我已经准备完毕!
7号运动员我已经准备完毕!
8号运动员我已经准备完毕!
6号运动员我已经准备完毕!
5号运动员我已经准备完毕!
3号运动员我已经准备完毕!
9号运动员我已经准备完毕!
1号运动员我已经准备完毕!
裁判:所有运动员准备完毕,开始...
10号运动员:开跑...
8号运动员:开跑...
3号运动员:开跑...
1号运动员:开跑...
2号运动员:开跑...
9号运动员:开跑...
5号运动员:开跑...
6号运动员:开跑...
7号运动员:开跑...
4号运动员:开跑...
总结:CountDownLatch通过AQS里面的共享锁来实现的,在创建CountDownLatch时候,会传递一个参数count,该参数是锁计数器的初始状态,表示该共享锁能够被count个线程同时获取。当某个线程调用CountDownLatch对象的await方法时候,该线程会等待共享锁可获取时,才能获取共享锁继续运行,而共享锁可获取的的条件是state == 0,而锁倒数计数器的初始值为count,每当一个线程调用该CountDownLatch对象的countDown()方法时候,计数器才-1,所以必须有count个线程调用该countDown()方法后,锁计数器才为0,这个时候等待的线程才能继续运行。
JUC系列回顾之-CountDownLatch底层原理和示例的更多相关文章
- 并发编程JUC系列AQS(CountDownLatch、CyclicBarrier、Semaphore)
一.CountDownLatch package com.jonychen.test; import java.util.concurrent.CountDownLatch; import java. ...
- Mybaits 源码解析 (五)----- 面试源码系列:Mapper接口底层原理(为什么Mapper不用写实现类就能访问到数据库?)
刚开始使用Mybaits的同学有没有这样的疑惑,为什么我们没有编写Mapper的实现类,却能调用Mapper的方法呢?本篇文章我带大家一起来解决这个疑问 上一篇文章我们获取到了DefaultSqlSe ...
- JUC回顾之-CyclicBarrier底层实现和原理
1.CyclicBarrier 字面意思是可循环(Cyclic)使用的屏障(Barrier).它要做的事情是让一组线程到达一个屏障(同步点)时被阻塞,直到最后一个线程到达屏障时候,屏障才会开门.所有被 ...
- Java多线程系列--“JUC锁”10之 CyclicBarrier原理和示例
概要 本章介绍JUC包中的CyclicBarrier锁.内容包括:CyclicBarrier简介CyclicBarrier数据结构CyclicBarrier源码分析(基于JDK1.7.0_40)Cyc ...
- Java多线程系列--“JUC锁”11之 Semaphore信号量的原理和示例
概要 本章,我们对JUC包中的信号量Semaphore进行学习.内容包括:Semaphore简介Semaphore数据结构Semaphore源码分析(基于JDK1.7.0_40)Semaphore示例 ...
- Java并发编程系列-(8) JMM和底层实现原理
8. JMM和底层实现原理 8.1 线程间的通信与同步 线程之间的通信 线程的通信是指线程之间以何种机制来交换信息.在编程中,线程之间的通信机制有两种,共享内存和消息传递. 在共享内存的并发模型里,线 ...
- (前篇:NIO系列 推荐阅读) Java NIO 底层原理
出处: Java NIO 底层原理 目录 1.1. Java IO读写原理 1.1.1. 内核缓冲与进程缓冲区 1.1.2. java IO读写的底层流程 1.2. 四种主要的IO模型 1.3. 同步 ...
- 多线程进阶——JUC并发编程之CountDownLatch源码一探究竟
1.学习切入点 JDK的并发包中提供了几个非常有用的并发工具类. CountDownLatch. CyclicBarrier和 Semaphore工具类提供了一种并发流程控制的手段.本文将介绍Coun ...
- 抛开 Spring ,你知道 MyBatis 加载 Mapper 的底层原理吗?
原文链接:抛开 Spring ,你知道 MyBatis 加载 Mapper 的底层原理吗? 大家都知道,利用 Spring 整合 MyBatis,我们可以直接利用 @MapperScan 注解或者 @ ...
随机推荐
- php中单例模式的解析说明
<?php //单例模式 class Dbconn{ private static $_instance=null; protected static $_counter=0; protecte ...
- 在不借助其他工具的情况下破解Windows开机密码
文章:http://www.cnblogs.com/vforbox/p/4828855.html#!comments. 从该文章我们也可以得到一个快速启动某个程序的方法:将自己常用的程序命名为seth ...
- 高速公路(Highway,ACM/ICPC SEERC 2005,UVa1615)
I think: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <m ...
- ifconfig 工具
ifconfig 工具 ifconfig 命令常用格式: 格式:ifconfig显示当前激活的网络接口信息. 格式:ifconfig {INTERFACE}显示指定网络接口的信息.比如:eth0, e ...
- WAF绕过小结
WAF介绍 什么是WAF? Web应用防火墙是通过执行一系列针对HTTP/HTTPS的安全策略来专门为Web应用提供保护的一款产品. 基本/简单绕过方法: 1.注释符 http://www.site. ...
- 利用sourcemap来调试sass
最近项目用上了sass,作为css的预处理器,它可以让我们用程序化的思维书写样式,极大的简化了css的开发,实在是前端居家旅行必备的利器. 我们都知道,在项目中,样式的频繁调试是不可避免的,用上sas ...
- 小希的迷宫(MST单棵树判断法则)
小希的迷宫 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total Submi ...
- Linux/Ubuntu tree 命令以树形结构显示文件夹目录结构
1.安装命令工具 sudo apt-get -y install tree 2.可以查看关于tree命令的帮助信息 $ tree --help usage: tree [-adfghilnpqrstu ...
- [2012-4-10]ThinkPHP框架被爆任意代码执行漏洞(preg_replace)
昨日(2012.04.09)ThinkPHP框架被爆出了一个php代码任意执行漏洞,黑客只需提交一段特殊的URL就可以在网站上执行恶意代码. ThinkPHP作为国内使用比较广泛的老牌PHP MVC框 ...
- unity StreamingAssets路径
原地址:http://blog.csdn.net/nateyang/article/details/8493791 我们在读写例如XML和TXT文件的时候,在电脑上和手机上路径不一致,造成了很多麻烦, ...