今天我们要来唠的就是Java并发工具包下的CountDownLatch 和CyclicBarrier,他们两者的使用和异同。
1.CountDownLatch的作用:是一个线程等待其他工作线程完成工作以后才能再继续执行,相当于加强版的join。
(换生活中通俗点的例子,就是我们玩王者荣耀游戏的时候,必须10个玩家都进入游戏,游戏才能进入选人界面。我们玩家就相当于工作线程,而游戏选人界面就相当于主线程)
2.这JUC的类中是使用await() 进行阻塞等待的,而使用countDown() 负责计数器-1(即我们10个玩家都进入时,计数器减为0,便可进入游戏。)
3.一般使用场景:当我们主线程需要等待初始化线程完成后,才能进一步操作(主线程的执行是由外部线程决定的)
代码演示:
业务需求:我们有5个初始化线程,6个扣除点。只有等扣除完毕以后,主线程和业务线程才能继续自己的工作。
定义初始化线程
(作简单的打印工作,没初始化一个,扣减一次。)
//初始化线程
private static class InitThread implements Runnable{
@Override
public void run() {
System.out.println("Thread_"+Thread.currentThread().getId()
+" ready init work...");
latch.countDown(); //初始化线程完成工作了,
for (int i = 0; i < 2; i++) {
System.out.println("Thread_"+Thread.currentThread().getId()
+"...........continue do its work");
}
}
}
定义业务线程
(业务线程调用await() 方法进入阻塞状态等待,扣减点减为0才放行。)
//业务线程
private static class BusiThread implements Runnable{
@Override
public void run() {
try {
latch.await(); //等待初始化线程完成工作
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i = 0; i < 3; i++){
System.out.println("BusiThread_"+ Thread.currentThread().getId()
+" do business");
}
}
}
完整代码
(在main线程中new出4个初始化线程和一个单独的线程(主要是为了证明一个线程也可以扣减两次),并new出一个业务线程,让业务线程和主线程都通过await() 方法进行阻塞,直到扣除点减为0放行为止,观察控制台的输出)
package 并发工具类;
import java.util.concurrent.CountDownLatch;
/**
* 类说明:演示CountDownLatch,有5个初始化线程,6个扣除点
* 扣除完毕以后,主线程和业务线程才能继续自己的工作
*/
public class UseCountDownLatch {
static CountDownLatch latch = new CountDownLatch(6);
//初始化线程
private static class InitThread implements Runnable{
@Override
public void run() {
System.out.println("Thread_"+Thread.currentThread().getId()
+" ready init work...");
latch.countDown(); //初始化线程完成工作了,
for (int i = 0; i < 2; i++) {
System.out.println("Thread_"+Thread.currentThread().getId()
+"...........continue do its work");
}
}
}
//业务线程
private static class BusiThread implements Runnable{
@Override
public void run() {
try {
latch.await(); //等待初始化线程完成工作
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i = 0; i < 3; i++){
System.out.println("BusiThread_"+ Thread.currentThread().getId()
+" do business");
}
}
}
public static void main(String[] args) throws InterruptedException {
//单独的初始化线程,初始化分为2步,需要扣减两次
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1);
System.out.println("Thread_"+Thread.currentThread().getId()
+" ready init work step 1st.....");
latch.countDown(); //每完成一步初始化工作,扣减一次
System.out.println("begin 2nd.....");
Thread.sleep(1);
System.out.println("Thread_"+Thread.currentThread().getId()
+" ready init work step 2nd.....");
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new BusiThread()).start(); //启动业务线程
for (int i = 0; i <= 3; i++) { //启动4个初始化线程
Thread thread = new Thread(new InitThread());
thread.start();
}
latch.await();
System.out.println("Main do its work....");
}
}
控制台输出(可以看到业务线程和主线程确实要等待初始化线程初始完毕,即6个扣除点减为0才会继续工作)
1.一组线程运行时,有一定的先后顺序。先到达栅栏的线程会被阻塞,直到这一组线程都到达该屏障后,屏障才会开放,所有阻塞的线程才能继续运行。
(举我们生活中的例子,比如我们4个人一起参加一个团队游戏,每个人需要通过的关卡不一样。但我们都会拿到一把钥匙,并最终来到胜利之门,这个门需要4把钥匙都集其才会打开,我们才能一起获得比赛的胜利)
区别:使用CyclicBarrier, 我们这组线程向下执行的条件由我们自身决定。而使用CountDownLatch, 我们所能决定的是外部线程,只有我们这组线程把扣除点扣除完之后,外面的线程才能执行、
2. 相关的构造方法
CyclicBarrier(int parties)
CyclicBarrier(int parties, Runnable barrierAction)
//屏障开发,barrierAction定义的任务也会执行
//应用场景:当有多个表单做处理,等这些表单处理的线程完成后,再开启屏障处理表单汇总工作。
3.代码示例
说明:我们构建出了5个栅栏,5个负者工作的线程(他们会因为一个随机数而进入休眠状态,这样5个线程到达屏障的时间可能不一样),并通过一个CollectThread来收集这5个工作线程的结果(由于我们使用的是携带线程的构造参数,它会随着栅栏开放而自动执行)
private static CyclicBarrier barrier = new CyclicBarrier(5,
new CollectThread());
完整代码
package 并发工具类;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CyclicBarrier;
// CyclicBarrier的使用
public class UseCyclicBarrier {
private static CyclicBarrier barrier = new CyclicBarrier(5,
new CollectThread());
private static ConcurrentHashMap<String, Long> resultMap
= new ConcurrentHashMap<>(); //存放子线程工作结果的容器
public static void main(String[] args) {
for(int i = 0; i <=4; i++){
new Thread(new SubThread()).start();
}
}
//负者屏障开放以后的工作
private static class CollectThread implements Runnable{
@Override
public void run() {
StringBuilder result = new StringBuilder();
for(Map.Entry<String,Long> workResult : resultMap.entrySet()){
result.append("["+workResult.getValue()+"]");
}
System.out.println(" the result = "+ result);
System.out.println("do other business...");
}
}
//工作线程
private static class SubThread implements Runnable{
@Override
public void run() {
long id = Thread.currentThread().getId();//线程本身的处理结果
resultMap.put(Thread.currentThread().getId()+"", id);
Random r = new Random(); //随机决定工作线程是否睡眠
try{
if(r.nextBoolean()){
Thread.sleep(2000 + id);
System.out.println("Thread_" + id
+"... do something ");
}
System.out.println(id + "....is await");
//await()也是一个计数,等待全部完成之后就会放行
barrier.await();
Thread.sleep(1000+id);
//这句打印等等5个线程一起到达屏障才会执行。
System.out.println("Thread_" + id + "...do its business");
}catch (Exception e){
e.printStackTrace();
}
}
}
}
控制台输出
CountDownLatch 和CyclicBarrier解析