countDownLatch
countDownLatch是一个同步工具类,允许一个线程或者多个线程同时等待,直到其他线程的操作完毕再执行。
coutDownLatch提供了2个方法,一个是countDown,一个是await,countDownlatch初始化的时候,会传入一个整数,赋值给内部计数器state,调用await方法会阻塞当前线程,并将当前线程加入到等到队列中,直到state等于0或者当前线程被中断,调用countDown方法,会使state的值减一,如果state等于0则唤醒等待队列中的线程。
适应场景:
1、场景一:在日常测试高并发场景时候,通常需要很多个线程同时启动,最后在汇总结果,这里可以在子线程countDown方法,子线程执行完毕就减一,主线程使用await方法阻塞,等到state为0时,唤醒主线程。
2、场景二:服务端需要同时请求多个接口,然后需要对这些接口的响应结果进行统计,如果单线程去请求这些接口,那么串行的超时可能性比较大,这里可以使用countDownLatch让多个子系统并发执行任务。
使用案例
import java.util.Date;
import java.util.concurrent.CountDownLatch;
public class CountDownTest {
public static void main(String[] args) throws InterruptedException {
final CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
new Thread() {
@Override
public void run() {
try {
System.out.println("子线程" + Thread.currentThread().getName() + "开始执行");
Thread.sleep(1000);// 模式执行过程1秒
System.out.println("子线程" + Thread.currentThread().getName() + "执行完毕" + countDownLatch.getCount());
countDownLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
countDownLatch.await();
System.out.println("全部执行完毕,主线程开始执行");
}
}
CyclicBarrier
CyclicBarrier提供2个构造器:
public CyclicBarrier(int parties, Runnable barrierAction) {
}
public CyclicBarrier(int parties) {
}
参数parties指让多少个线程或者任务等待至barrier状态;参数barrierAction为当这些线程都达到barrier状态时会执行的内容。
CyclicBarrier中最重要的方法就是await方法。
CyclicBarrier和countDownLatch的区别是
CountDownLatch | CyclicBarrier |
---|---|
减计数方式 | 加计数方式 |
计算为0时释放所有等待的线程 | 计数达到指定值时释放所有等待线程 |
计数为0时,无法重置 | 计数达到指定值时,计数置为0重新开始 |
调用countDown()方法计数减一,调用await()方法只进行阻塞,对计数没任何影响 | 调用await()方法计数加1,若加1后的值不等于构造方法的值,则线程阻塞 |
不可重复利用 | 可重复利用 |
重要的区别就是计数到达指定值时候,CountDownLatch不能重置,CyclicBarrier可以重置。
比如一个场景,一家公司要全体员工进行团建活动,活动内容为翻越三个栅栏,求所有人在翻越当前栅栏之后再开始翻越下一个栅栏,这里就可以用CyclicBarrier实现,假设有3个员工,每个员工就是一个线程。
package com.countdownlatch;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierTest1 {
public static void main(String[] args) {
final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss SSS");
// 定义3,下面就要await 3次才能解除阻塞状态,runnable里面的操作是最好一个到达的线程
CyclicBarrier barrier = new CyclicBarrier(3, new Runnable() {
@Override
public void run() {
try {
System.out.println(sdf.format(new Date()) + " " + "翻越了一个栅栏,集体休息3秒");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
for (int i = 0; i < 3; i++) {
new Thread(new MyRunnable(barrier), "员工" + i).start();
}
}
private static class MyRunnable implements Runnable {
private CyclicBarrier barrier;
public MyRunnable(CyclicBarrier barrier) {
this.barrier = barrier;
}
@Override
public void run() {
final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss SSS");
// 比如有4个栅栏
for (int i = 0; i < 4; i++) {
try {
Random rand = new Random();
int randomNum = rand.nextInt((3000 - 1000) + 1) + 1000;//产生1000到3000之间的随机整数
Thread.sleep(randomNum);
System.out.println(sdf.format(new Date()) + " " + Thread.currentThread().getName() + ", 通过了第" + i + "个栅栏, 使用了 " + ((double) randomNum / 1000) + "s");
this.barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
}
}
执行结果:
注意点:
- 1)对于指定计数值 parties,若由于某种原因,没有足够的线程调用 CyclicBarrier 的 await,则所有调用 await 的线程都会被阻塞
- 2)同样的 CyclicBarrier 也可以调用 await(timeout, unit), 设置超时时间,在设定时间内,如果没有足够线程到达, 则解除阻塞状态,继续工作;
- 3)通过 reset 重置计数,会使得进入 await 的线程出现 BrokenBarrierException;
- 4 )如果采用是 CyclicBarrier(int parties, Runnable barrierAction) 构造方法,执行 barrierAction 操作的是最后一个到达的线程