14 JUC的Semaphore,CountDownLatch,Cyclicbarrier的应用与原理
目录
1 Semaphore的使用与原理 [ˈsɛməˌfɔr, -ˌfor]
1-1 概述
应用场景:用来限制能同时访问共享资源的线程上限
实例:每个时刻最多三个线程访问资源
package chapter8;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Semaphore;
@Slf4j(topic = "c.test19")
public class test19 {
public static void main(String[] args) {
// 1. 创建 semaphore 对象
// permits参数限制访问线程数目, fair参数控制是否公平
Semaphore semaphore = new Semaphore(3);
// 2. 10个线程同时运行,但同一时刻最多只有三个线程获得资源
// semaphore限制了访问资源的线程数目
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
log.debug("running...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("end...");
} finally {
semaphore.release();
}
}).start();
}
}
}
执行结果:同一时刻最多有三个线程拿到资源
21:16:12.378 [Thread-1] DEBUG c.test19 - running...
21:16:12.378 [Thread-2] DEBUG c.test19 - running...
21:16:12.378 [Thread-0] DEBUG c.test19 - running...
21:16:13.383 [Thread-1] DEBUG c.test19 - end...
21:16:13.383 [Thread-2] DEBUG c.test19 - end...
21:16:13.383 [Thread-0] DEBUG c.test19 - end...
21:16:13.383 [Thread-3] DEBUG c.test19 - running...
21:16:13.383 [Thread-4] DEBUG c.test19 - running...
21:16:13.383 [Thread-5] DEBUG c.test19 - running...
21:16:14.384 [Thread-4] DEBUG c.test19 - end...
21:16:14.384 [Thread-5] DEBUG c.test19 - end...
21:16:14.384 [Thread-6] DEBUG c.test19 - running...
21:16:14.385 [Thread-7] DEBUG c.test19 - running...
21:16:14.385 [Thread-3] DEBUG c.test19 - end...
21:16:14.385 [Thread-8] DEBUG c.test19 - running...
21:16:15.385 [Thread-8] DEBUG c.test19 - end...
21:16:15.386 [Thread-9] DEBUG c.test19 - running...
21:16:15.386 [Thread-7] DEBUG c.test19 - end...
21:16:15.386 [Thread-6] DEBUG c.test19 - end...
21:16:16.387 [Thread-9] DEBUG c.test19 - end...
1-2 Semaphore的应用:限制对共享资源的使用
1-2-1 应用场景
1)使用 Semaphore 限流,在访问高峰期时,让请求线程阻塞,高峰期过去再释放许可,当然它只适合限制单机线程数量,并且仅是限制线程数,而不是限制资源数
---(资源的数目与请求的线程(请求成功才分配资源,一个线程可能分配多种类型资源)的数目不是一个概念)。
2)用 Semaphore 实现简单连接池,对比『享元模式』下的实现(用wait notify),性能和可读性显然更好。
- 当一个连接对应一个资源,比如数据库连接池就是一个线程对应一个数据库连接,这种2)场景非常适合使用semaphore。
1-2-2 使用semaphore优化自定义的数据库连接池
使用wait/notify结合原子数组实现基于享元模式的连接池
- 之前的wait/notify主要在线程池资源全部分配完了调用wait使得的线程进入waitset,当有线程释放资源调用notify唤醒线程来使用资源
优化后代码
package chapter8;
import lombok.extern.slf4j.Slf4j;
import java.sql.*;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import java.util.concurrent.Executor;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicIntegerArray;
@Slf4j(topic = "c.pool")
class Pool{
// 01 连接池大小
private final int poolSize;
// 02 连接对象数组
private Connection[] connections;
// 03 连接状态数组,0表示空闲,1表示繁忙
private AtomicIntegerArray states;
// 04 使用semaphore管理线程池的容量
private Semaphore semaphore;
Pool(int poolSize){
this.poolSize = poolSize;
this.semaphore = new Semaphore(poolSize);
this.states = new AtomicIntegerArray(new int[poolSize]);
this.connections = new Connection[poolSize];
for(int i = 0;i < poolSize;++i){
this.connections[i] = new MockConnection();
}
}
public Connection borrow(){
try {
semaphore.acquire(); // 获取资源的许可,如果没有资源则会让线程
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i = 0;i < poolSize;++i){
if(states.get(i) == 0){
// 注意:这里必须采用CAS操作确保多个线程状态变量的安全性
states.compareAndSet(i,0,1);
log.warn("borrow connection {}",i);
return connections[i];
}
}
return null;
}
public void free(Connection conn){
for(int i = 0;i < poolSize;++i){
if(connections[i] == conn){
states.set(i,0);
log.warn("free connection {}",i);
semaphore.release(); // 释放许可
break;
}
}
}
}
public class test20 {
public static void main(String[] args) {
Pool pool = new Pool(2);
for(int i = 0;i < 5;++i){
new Thread(()->{
Connection tmp = pool.borrow();
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
pool.free(tmp);
}).start();
}
}
}
// mock:虚假的
// 这里实现了一个虚假的连接池对象
class MockConnection implements Connection{
.....
}
执行结果
21:52:13.475 [Thread-1] WARN c.pool - borrow connection 0
21:52:13.475 [Thread-0] WARN c.pool - borrow connection 0
21:52:13.675 [Thread-0] WARN c.pool - free connection 0
21:52:13.675 [Thread-2] WARN c.pool - borrow connection 0
21:52:14.364 [Thread-1] WARN c.pool - free connection 0
21:52:14.364 [Thread-3] WARN c.pool - borrow connection 0
21:52:14.531 [Thread-2] WARN c.pool - free connection 0
21:52:14.531 [Thread-4] WARN c.pool - borrow connection 0
21:52:14.546 [Thread-4] WARN c.pool - free connection 0
21:52:14.916 [Thread-3] WARN c.pool - free connection 0
总结:semaphore的合理使用取代了wait/notify,使得代码更加简洁吗,容易读
1-3 Semaphore的原理(AQS等待队列节点模式是Shared的)
概述:其基本思想与读写锁的读锁的获取与释放基本一致
1-3-1 Semaphore的构造方法
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
- 可以看到Semaphore也有2类同步器:公平同步器与非公平同步器
static final class FairSync extends Sync {
private static final long serialVersionUID = 2014338818796000944L;
FairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
for (;;) {
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
}
========================================================================================
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 1192457210091910933L;
Sync(int permits) { // 许可的数目被存储在AQS的state中
setState(permits);
}
final int getPermits() {
return getState();
}
......
}
- 可以看到许可的数目被存储在AQS的state中。
1-3-1 Semaphore的acquire方法原理(获取资源的数目)
基本思路
分为二种情况讨论:
情况1:当前资源足够,则修改资源的数目
情况2:资源不足的情况下,则将线程放入AQS的的等待队列。并用park停止运行。
一些细节:
1)state的维护(CAS机制)
2)队列的维护(CAS机制)
3)第一次失败后,还会进行几次尝试获取资源
源码分析
semaphore.acquire(); // 获取资源的许可,如果没有资源则会让线程阻塞
==================================================================================
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
==================================================================================
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0) // step1:尝试获取args数量的许可,如果申请失败返回-1
doAcquireSharedInterruptibly(arg);
// step2:将线程加入AQS阻塞队列,可能尝试几次,之后调用park方法让线程停止运行
}
===tryAcquireShared(arg):的两个公平/非公平实现===================================
final int nonfairTryAcquireShared(int acquires) {su
for (;;) {
int available = getState();
int remaining = available - acquires; //可获的许可数-线程申请的许可数
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining; // 如果资源足够返回剩下的许可数
}
}
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
==================================================================================
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
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);
}
}
======================================================================================
获取资源的图示过程
1-3-1 Semaphore的release方法原理
基本思想:
当前线程释放自由拥有的的资源,并修改state,根据当前节点的waitstate判断是否去AQS的等待队列中去唤醒一个线程去获取资源。
细节:
1)semaphore的AQS阻塞队列的节点时Shared的状态,只要资源充足唤醒会propagate.
semaphore.release(); // 释放当前线程拥有的许可
====================================================================
public void release() {
sync.releaseShared(1);
}
=====================================================================
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
========================================================================
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 void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) { // SIGNAL表明存在后续的节点进行唤醒
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
======================================================================
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread); //唤醒线程
}
=============唤醒后的线程进行propagate唤醒==================================================
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
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); // 只要资源充足唤醒会propagate
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()) // 在此被唤醒
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
=========================================================================================
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
setHead(node);
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared(); // 再次唤醒后面的节点
}
}
释放资源的图示过程
2 CountDown(倒计时锁)的使用与应用
[kaʊnt]
3-1 概述
应用场景:用来进行线程同步协作,等待所有线程完成倒计时。
- 构造参数用来初始化等待计数值
- await() 用来等待计数归零
- countDown() 用来让计数减一
与join的区别:join相对而言属于比较底层的API,使用比较繁琐,且必须等待线程结束,CountDownLatch提供了更加灵活的与线程同步的方式(线程可以在完成某个条件调用countDown()方法)。
源码速看
本质上利用了AQS的state变量计数实现了类似join的功能
- countDown() 让锁的state减去1
- await() 则当state不等于0的情况下,阻塞调用线程。
package java.util.concurrent;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
public class 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();
}
// 当state==0能够获得锁
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
// 释放锁的时候,当state为0的时候,返回false;
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;
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
// 只有当state为0的时候,才能通过该方法获得锁,否则调用线程会一直阻塞。
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() + "]";
}
}
======================================================================================
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
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);
}
}
3-2 简单应用
package chapter8;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CountDownLatch;
@Slf4j(topic = "c.test21")
public class test21 {
public static void main(String[] args) throws InterruptedException {
/*本质上CountDownLatch就是让state充当了计数功能,
利用了AQS继承的锁在state不为0的情况下无法获得锁阻塞的特性,实现了类似join函数的功能*/
CountDownLatch latch = new CountDownLatch(3);
new Thread(()->{
log.warn("begin");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.warn("end...");
latch.countDown(); //线程执行完毕,让state--
},"t1").start();
new Thread(()->{
log.warn("begin");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.warn("end...");
latch.countDown(); //线程执行完毕,让state--
},"t2").start();
new Thread(()->{
log.warn("begin");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.warn("end...");
latch.countDown(); //线程执行完毕,让state--
},"t3").start();
log.warn("start waiting");
latch.await(); // state == 0的时候,才会往下执行,否则进入AQS等待队列
log.warn("end waiting");
}
}
执行结果
- 主线程等待其他三个线程执行完毕才往下执行
10:01:58.911 [t2] WARN c.test21 - begin
10:01:58.911 [t3] WARN c.test21 - begin
10:01:58.911 [main] WARN c.test21 - start waiting
10:01:58.911 [t1] WARN c.test21 - begin
10:01:59.917 [t1] WARN c.test21 - end...
10:01:59.917 [t2] WARN c.test21 - end...
10:02:01.916 [t3] WARN c.test21 - end...
10:02:01.916 [main] WARN c.test21 - end waiting
3-3 CountDownLatch配合线程池使用
package chapter8;
import ch.qos.logback.core.util.ExecutorServiceUtil;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Slf4j(topic = "c.test22")
public class test22 {
public static void main(String[] args) throws InterruptedException {
/*本质上CountDownLatch就是让state充当了计数功能,利用了AQS继承的锁在state不为0的情况下无法获得锁阻塞的特性,实现
* 了类似join函数的功能*/
CountDownLatch latch = new CountDownLatch(3);
ExecutorService pool = Executors.newFixedThreadPool(4);
pool.submit(()->{
log.warn("t1 begin");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.warn("t1 end...");
latch.countDown(); //线程执行完毕,让state--
});
pool.submit(()->{
log.warn("t2 begin");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.warn("t2 end...");
latch.countDown(); //线程执行完毕,让state--
});
pool.submit(()->{
log.warn("t3 begin");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.warn("t3 end...");
latch.countDown(); //线程执行完毕,让state--
});
pool.submit(()->{
log.warn("start waiting");
try {
latch.await(); // state == 0的时候,才会往下执行
} catch (InterruptedException e) {
e.printStackTrace();
}
log.warn("end waiting");
});
}
}
执行结果
- 线程池中三个线程负责处理,一个线程负责汇总结果
10:34:19.232 [pool-1-thread-3] WARN c.test22 - t3 begin
10:34:19.232 [pool-1-thread-1] WARN c.test22 - t1 begin
10:34:19.232 [pool-1-thread-4] WARN c.test22 - start waiting
10:34:19.232 [pool-1-thread-2] WARN c.test22 - t2 begin
10:34:20.238 [pool-1-thread-2] WARN c.test22 - t2 end...
10:34:20.238 [pool-1-thread-3] WARN c.test22 - t3 end...
10:34:20.238 [pool-1-thread-1] WARN c.test22 - t1 end...
10:34:20.238 [pool-1-thread-4] WARN c.test22 - end waiting
3-4 CountDownLatch的典型应用场景介绍
场景1:多个游戏资源并发加载完成通知
在这个场景下,某个线程进入到下一个流程,需要多个资源加载好,这里假定为10个,可以采用线程池,令该线程提交10个任务分别加载一个资源,然后调用countDownLatch.await去等待所有资源加载完毕,每个任务准备好资源就countDown。
实例
package chapter8;
import java.util.*;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
public class test23{
public static void main(String[] args) throws InterruptedException {
AtomicInteger num = new AtomicInteger(0);
ExecutorService service = Executors.newFixedThreadPool(10, (r) -> {
return new Thread(r, "t" + num.getAndIncrement());
});
CountDownLatch latch = new CountDownLatch(10);
String[] all = new String[10];
Random r = new Random();
for (int j = 0; j < 10; j++) { // 模拟提交10个线程任务
int x = j;
// 线程池中每个任务模拟加载资源,当资源加载完毕,调用countDown(),将state--
service.submit(() -> {
for (int i = 0; i <= 100; i++) {
try {
Thread.sleep(r.nextInt(100));
} catch (InterruptedException e) {
}
all[x] = Thread.currentThread().getName() + "(" + (i + "%") + ")";
System.out.print("\r" + Arrays.toString(all));
}
latch.countDown();
});
}
latch.await();
System.out.println("\n游戏开始...");
service.shutdown();
}
}
执行结果
- 10个资源都加载好了,才进行下一个流程
[t0(100%), t1(100%), t2(100%), t3(100%), t4(100%), t5(100%), t6(100%), t7(100%),t8(100%), t9(100%)]
游戏开始...
场景2微服务场景下多个RPC远程调用并发执行
在这个场景下,当前用户的请求,需要多个服务器资源,比如需要商品信息,订单信息,快递信息共三个信息,则需要进行三次远程调用,如果顺序执行的话,显然效率比较低下。
此时可以采用多线程的方式利用线程池配合CountDownLatch并发的去获取资源(采用线程的future对象返回线程执行完返回的结果),节约时间。
3 Cyclicbarrier(循环栅栏)的使用以及注意点(批量任务循环执行)
[ˈsaɪklɪk] [ˈbæriər]
3-1 Cyclicbarrier概述
作用:用来进行线程协作,等待线程满足某个计数。构造时设置『计数个数』,每个线程执
行到某个需要“同步”的时刻调用 await() 方法进行等待,当等待的线程数满足『计数个数』时,继续执行。
- 每次调用 await()会令state减去1,当state为0,会调用构造函数的第二个参数runable接口。
应用场景:应用于需要重复执行一批任务。可以看成循环版的CountDownLatch。
3-2 实例
package chapter8;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.*;
@Slf4j(topic = "c.test24")
public class test24 {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(2);
/*参数1:线程池的大小, 参数2: state为0,调用实现的runable接口*/
CyclicBarrier barrier = new CyclicBarrier(2, ()-> {
log.debug("task1, task2 finish...");
});
/* 将线程池中的2个task循环执行三次,CyclicBarrier计数为0的时候会自定恢复到初值,即
这个类是可重用的。*/
for (int i = 0; i < 3; i++) { // task1 task2 task1
service.submit(() -> {
log.debug("task1 begin...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
barrier.await(); // 2-1=1
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
});
service.submit(() -> {
log.debug("task2 begin...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
barrier.await(); // 1-1=0
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
});
}
service.shutdown();
}
}
执行结果
11:57:12.345 [pool-1-thread-2] DEBUG c.test24 - task2 begin...
11:57:12.345 [pool-1-thread-1] DEBUG c.test24 - task1 begin...
11:57:14.349 [pool-1-thread-2] DEBUG c.test24 - task1, task2 finish...
11:57:14.349 [pool-1-thread-2] DEBUG c.test24 - task1 begin...
11:57:14.349 [pool-1-thread-1] DEBUG c.test24 - task2 begin...
11:57:16.349 [pool-1-thread-1] DEBUG c.test24 - task1, task2 finish...
11:57:16.349 [pool-1-thread-1] DEBUG c.test24 - task1 begin...
11:57:16.349 [pool-1-thread-2] DEBUG c.test24 - task2 begin...
11:57:18.349 [pool-1-thread-2] DEBUG c.test24 - task1, task2 finish...
注意点
循环执行固定数量任务,一定要确保线程池大小与CyclicBarrier第一个参数一致
Executors.newFixedThreadPool(2);
CyclicBarrier barrier = new CyclicBarrier(2, ()-> {
log.debug("task1, task2 finish...");
});
线程池大小与初始计数不一致:
Executors.newFixedThreadPool(3);
CyclicBarrier barrier = new CyclicBarrier(2, ()-> {
log.debug("task1, task2 finish...");
});
执行结果
11:55:39.325 [pool-1-thread-3] DEBUG c.test24 - task1 begin...
11:55:39.325 [pool-1-thread-2] DEBUG c.test24 - task2 begin...
11:55:39.325 [pool-1-thread-1] DEBUG c.test24 - task1 begin...
11:55:40.329 [pool-1-thread-3] DEBUG c.test24 - task1, task2 finish...
11:55:40.329 [pool-1-thread-3] DEBUG c.test24 - task2 begin...
11:55:40.329 [pool-1-thread-1] DEBUG c.test24 - task1 begin...
11:55:41.330 [pool-1-thread-1] DEBUG c.test24 - task1, task2 finish...
11:55:41.330 [pool-1-thread-1] DEBUG c.test24 - task2 begin...
11:55:43.330 [pool-1-thread-1] DEBUG c.test24 - task1, task2 finish...
从结果可以看到在第一个循环中任务还没有执行完前,多余的空闲进程会执行下一个循环中同样任务。