CountDownLatch

众所周知,它能解决一个任务必须在其他任务完成的情况下才能执行的问题,代码层面来说就是只有计数countDown到0的时候,await处的代码才能继续向下运行,例如:

import java.util.*;
import java.util.concurrent.*; public class Main {
public static void main(String[] args) throws Exception { CountDownLatch latch = new CountDownLatch(3); ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 15, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5));
Future<Integer>[] futures = new Future[3];
for (int i = 0; i < 3; i++){
futures[i] = executor.submit(() -> {
Random rand = new Random();
int n = rand.nextInt(100);
int result = 0;
for (int j = 0; j < n; j++){
result += j;
}
System.out.println(result + "|" + Thread.currentThread().getName());
latch.countDown();
return result;
});
}
latch.await();
System.out.println("合计每个任务的结果:" + (futures[0].get()+futures[1].get()+futures[2].get()));
} }

运行结果:

源码

实际上内部十分简单,里面只有一个AQS的子类

private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L; // 它把AQS的state(同步状态)作为计数器,在AQS里,state是个volatile标记的int变量
Sync(int count) {
setState(count);
} int getCount() {
return getState();
} protected int tryAcquireShared(int acquires) {
// 同步状态为0,则返回1,否则返回-1
return (getState() == 0) ? 1 : -1;
} protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
// 如果状态为0则返回false
if (c == 0)
return false;
// 计数器减1
int nextc = c-1;
// CAS操作,如果内存中的同步状态值等于期望值c,那么将同步状态设置为给定的更新值nextc
if (compareAndSetState(c, nextc))
return nextc == 0; // 当计数器减到0,返回true
}
}
} public void countDown() {
sync.releaseShared(1);
} public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}

下面看具体做了什么事情

先来看await

public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 当计数器不等于0,返回-1,证明还有任务未执行完,进入下面方法等待
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
} private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
// 把当前线程包装成Node放入等待队列
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
// 获取当前线程的前驱节点,以检查等待状态
final Node p = node.predecessor();
if (p == head) {
// 如果计数器等于0,返回1,证明此时阻塞可以解除了
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);
}
}

上面的过程可以总结为:当进入await方法后,如果此时计数器不为0,则进入死循环一直检查计数器的值,直到为0退出,此时停止等待。

再来看countDown

public final boolean releaseShared(int arg) {
// 尝试计数器减1,只有减到0才会返回true
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
} private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
// 等待状态为SIGNAL
if (ws == Node.SIGNAL) {
// 把当前节点的等待状态从SIGNAL设置成0,如果设置失败则继续循环。
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; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}

countDown的过程可以总结为:尝试将计数器-1,直到为0,为0的时候通知等待线程。

CycleBarrier

栏栅的作用就是让指定的一批任务能够同时开始执行,比如

import java.util.*;
import java.util.concurrent.*; public class Main {
public static void main(String[] args) throws Exception {
CyclicBarrier cyclicBarrier = new CyclicBarrier(3); ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 15, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5));
Future<Integer>[] futures = new Future[3];
for (int i = 0; i < 3; i++){
futures[i] = executor.submit(() -> {
System.out.println("await|" + Thread.currentThread().getName());
cyclicBarrier.await();
Random rand = new Random();
int n = rand.nextInt(100);
int result = 0;
for (int j = 0; j < n; j++){
result += j;
}
System.out.println(result + "|" + Thread.currentThread().getName());
return result;
});
}
} }

运行结果

源码

进来之后首先发现的是成员变量

/** 用来保护栅栏入口的锁 */
private final ReentrantLock lock = new ReentrantLock();
/** 等待条件,直到计数器为0 */
private final Condition trip = lock.newCondition();
/** 参与线程的个数 */
private final int parties;
/* 计数器为0时要运行的命令,由用户定义 */
private final Runnable barrierCommand;
/** 当前等待的一代 */
private Generation generation = new Generation();
/**
* parties数量的等待线程。每一代等待的数量从parties到0。当调用nextGeneration或者breakBarrier方法时重置。
*/
private int count;

从这里可以看出,除了内部实现用的ReentrantLock,其工作过程无非:计数器不为0的时候线程等待;当等待线程全部就绪,也就是计数器减为0的时候重置计数器并通知所有线程继续运行。

导致计数器重置原因有两个:一个就是发生异常,将当前这一代标记为无效(broken=true);另一个就是正常就绪,开启下一代(new Generation)

核心方法dowait

// 情况一:timed=false,nanos=0L,代表一直阻塞
// 情况二:timed=true,nanos!=0L,代表在超时时间内阻塞
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 获取当前这一代
final Generation g = generation; // 如果当前这一代已经销毁,抛异常
if (g.broken)
throw new BrokenBarrierException();
// 测试当前线程是否被中断
if (Thread.interrupted()) {
// 将broken设置为true,代表这一代已经销毁,重置count;然后通知所有等待线程
breakBarrier();
throw new InterruptedException();
}
// count 减1
int index = --count;
// 如果减1之后变成0,证明等待线程全部就绪。
if (index == 0) { // tripped
boolean ranAction = false;
try {
// 如果用户定义了额外的命令,则执行
final Runnable command = barrierCommand;
if (command != null)
command.run();
ranAction = true;
// 开启下一代(通知所有等待线程,重置count,new一个新的Generation)
nextGeneration();
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
} // loop until tripped, broken, interrupted, or timed out
// 如果减1之后不等于0,也就是还有其它线程没有就绪,那么进入此循环,直到就绪或者被销毁,或者被中断和超时
for (;;) {
try {
if (!timed)
// 未定义超时,则一直阻塞
trip.await();
else if (nanos > 0L)
// 等待指定的超时时间
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
if (g == generation && ! g.broken) {
breakBarrier();
throw ie;
} else {
// We're about to finish waiting even if we had not
// been interrupted, so this interrupt is deemed to
// "belong" to subsequent execution.
Thread.currentThread().interrupt();
}
} if (g.broken)
throw new BrokenBarrierException(); if (g != generation)
return index; // 超时,则销毁这一代,通知所有等待线程并重置count
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}

总结

两个工具实现思路都很简单,唯一我思考的是,为什么CountDownLatch只能用一次?

CycleBarrier很明显,它无论正常执行或者发生异常中断都有重置count的逻辑。

而CountDownLatch则没有重置的逻辑,那么,到底是CountDownLatch不能重置还是仅仅因为没有重置的逻辑。为此我把CountDownLatch的代码照搬,然后加上了简单的重置方法,如下:

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer; public class MyCountDown { private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L; Sync(int count) {
setState(count);
} /**
* 新加
* @param count
*/
void reset(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; private final int count; public MyCountDown(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
this.count = count;
} public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
} public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
} public void countDown() {
sync.releaseShared(1);
} public long getCount() {
return sync.getCount();
} public String toString() {
return super.toString() + "[Count = " + sync.getCount() + "]";
} /**
* 新加
*/
public void reset(){
// 调用重置的方法
this.sync.reset(count);
}
}

测试:

import java.util.*;
import java.util.concurrent.*; public class Main {
public static void main(String[] args) throws Exception { MyCountDown myCountDown = new MyCountDown(3);
ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 15, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5));
Future<Integer>[] futures = new Future[3];
for (int i = 0; i < 3; i++){
futures[i] = executor.submit(() -> {
Random rand = new Random();
int n = rand.nextInt(100);
int result = 0;
for (int j = 0; j < n; j++){
result += j;
}
System.out.println(result + "|" + Thread.currentThread().getName());
Thread.sleep(new Random().nextInt(2000)); // 模拟耗时
myCountDown.countDown();
return result;
});
}
myCountDown.await();
System.out.println("第一次:" + (futures[0].get() + futures[1].get() + futures[2].get()));
myCountDown.reset(); // 重置 for (int i = 0; i < 3; i++){
futures[i] = executor.submit(() -> {
Random rand = new Random();
int n = rand.nextInt(100);
int result = 0;
for (int j = 0; j < n; j++){
result += j;
}
System.out.println(result + "|" + Thread.currentThread().getName());
Thread.sleep(new Random().nextInt(2000)); // 模拟耗时
myCountDown.countDown();
return result;
});
}
myCountDown.await();
System.out.println("如果重置无效,则这个信息会先于任务信息输出");
System.out.println("第二次:" + (futures[0].get() + futures[1].get() + futures[2].get()));
} }

输出

如果换成CountDownLatch

import java.util.*;
import java.util.concurrent.*; public class Main {
public static void main(String[] args) throws Exception { CountDownLatch latch = new CountDownLatch(3);
ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 15, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5));
Future<Integer>[] futures = new Future[3];
for (int i = 0; i < 3; i++){
futures[i] = executor.submit(() -> {
Random rand = new Random();
int n = rand.nextInt(100);
int result = 0;
for (int j = 0; j < n; j++){
result += j;
}
System.out.println(result + "|" + Thread.currentThread().getName());
Thread.sleep(new Random().nextInt(2000)); // 模拟耗时
latch.countDown();
return result;
});
}
latch.await();
System.out.println("第一次:" + (futures[0].get() + futures[1].get() + futures[2].get())); for (int i = 0; i < 3; i++){
futures[i] = executor.submit(() -> {
Random rand = new Random();
int n = rand.nextInt(100);
int result = 0;
for (int j = 0; j < n; j++){
result += j;
}
System.out.println(result + "|" + Thread.currentThread().getName());
Thread.sleep(new Random().nextInt(2000)); // 模拟耗时
latch.countDown();
return result;
});
}
latch.await();
System.out.println("如果重置无效,则这个信息会先于任务信息输出");
System.out.println("第二次:" + (futures[0].get() + futures[1].get() + futures[2].get()));
} }

输出

所以可以得出结论,CountDownLatch不是没有办法重置,只不过没有写相关逻辑。当然这个问题如果我说错了,望指正。

CycleBarrier与CountDownLatch原理的更多相关文章

  1. join和countDownLatch原理及区别详解

    先上结论 原理 join 原理:在当前线程中调用另一个线程线程 thread 的 join() 方法时,会调用该 thread 的 wait() 方法,直到这个 thread 执行完毕(JVM在 ru ...

  2. CountDownLatch原理分析

    CountDownLatch原理分析 CountDownLatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程执行完后再执行.例如,应用程序的主线程希望在负责启动框架服务的线程已经启动 ...

  3. Java多线程系列--“JUC锁”09之 CountDownLatch原理和示例

    概要 前面对"独占锁"和"共享锁"有了个大致的了解:本章,我们对CountDownLatch进行学习.和ReadWriteLock.ReadLock一样,Cou ...

  4. 【分布式锁】05-使用Redisson中Semaphore和CountDownLatch原理

    前言 前面已经写了Redisson大多的内容,我们再看看Redisson官网共有哪些组件: image.png 剩下还有Semaphore和CountDownLatch两块,我们就趁热打铁,赶紧看看R ...

  5. CountDownLatch原理详解

    介绍 当你看到这篇文章的时候需要先了解AQS的原理,因为本文不涉及到AQS内部原理的讲解. CountDownLatch是一种同步辅助,让我们多个线程执行任务时,需要等待线程执行完成后,才能执行下面的 ...

  6. CountDownLatch原理及使用场景

    CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量.每当一个线程完成了自己的任务后,计数器的值就会减1.当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁 上 ...

  7. CountDownLatch原理

    正如每个Java文档所描述的那样,CountDownLatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行.在Java并发中,countdownlatch的概念是一 ...

  8. Java多线程系列--“JUC锁”10之 CyclicBarrier原理和示例

    概要 本章介绍JUC包中的CyclicBarrier锁.内容包括:CyclicBarrier简介CyclicBarrier数据结构CyclicBarrier源码分析(基于JDK1.7.0_40)Cyc ...

  9. Java多线程系列--“JUC锁”11之 Semaphore信号量的原理和示例

    概要 本章,我们对JUC包中的信号量Semaphore进行学习.内容包括:Semaphore简介Semaphore数据结构Semaphore源码分析(基于JDK1.7.0_40)Semaphore示例 ...

随机推荐

  1. English--美式发音

    English|美式发音 本文,总结了自己在学习美音的一些感悟,希望大家学习愉快!enjoy~ 前言 目前所有的文章思想格式都是:知识+情感. 知识:对于所有的知识点的描述.力求不含任何的自我感情色彩 ...

  2. Linux中用postfix搭建邮件服务器实战详解

    Linux中用postfix搭建邮件服务器实战详解 postfix是Wietse Venema在IBM的GPL协议之下开发的MTA(邮件传输代理)软件.Postfix试图更快.更容易管理.更安全,同时 ...

  3. Struts2 OGNL表达式、ValueStack

    OGNL简介 OGNL,即Object-Graph Navigation Language,对象视图导航语言,是一种数据访问语言,比EL表达式更加强大: EL只能从11个内置对象中取值,且只能获取属性 ...

  4. Python Scrapy在windows上的安装方法

    如果想要学习爬虫,肯定会了解Scrapy,但安装Scrapy的方法相对于安装其他第三方库麻烦一点. 下面总结一下在我的电脑上安装Scrapy的方法,我的电脑是Windows10,32位操作系统.有如下 ...

  5. 错误记录PHP preg_match(): Compilation failed: range out of order in character class at offset 7 01_login.php</b> on line <b>13</b><br />

    <?php //响应头格式为json,编码为uft-8,注意有- header("Content-Type:application/json;charset=utf-8"); ...

  6. 2.dubbo 支持哪些通信协议?支持哪些序列化协议?说一下 Hessian 的数据

    作者:中华石杉 面试题 dubbo 支持哪些通信协议?支持哪些序列化协议?说一下 Hessian 的数据结构?PB 知道吗?为什么 PB 的效率是最高的? 面试官心理分析 上一个问题,说说 dubbo ...

  7. Django 练习班级管理系统二 -- 添加班级数据

    在上一篇中(Django 练习班级管理系统一 https://www.cnblogs.com/klvchen/p/11078174.html),使用的是莫泰对话框的方式提交数据,适用于数据量少的操作. ...

  8. 7. [mmc subsystem] host(第一章)——概述

    一.host简单说明 host,也可以理解为host controller,是指mmc总线上的主机端,mmc总线的控制器,每个host controller对应一条mmc总线. host contro ...

  9. springcloud学习之路: (三) springcloud集成Zuul网关

    网关就是做一下过滤或拦截操作 让我们的服务更加安全 用户访问我们服务的时候就要先通过网关 然后再由网关转发到我们的微服务 1. 新建一个网关服务Module 2. 依然选择springboot工程 3 ...

  10. python 函数,模块知识点运用示例

    给定验证码长度n,生成随机验证码,验证码由数字.字母组成(参考chr()内置方法) # 给定验证码长度n,生成随机验证码,验证码由数字.字母组成(参考chr()内置方法) # 第33-126号(共94 ...