Java中的CyclicBarrier和CountDownLatch

来源:互联网 发布:淘宝招聘兼职 编辑:程序博客网 时间:2024/06/05 06:53

一、前言

在平时的开发中,为了加快程序的响应速度充分利用CPU的资源,我们会采用多线程的方式进行编程,但是如果我们的线程之间如果存在一定的逻辑关系(例如:线程A需要等到线程B和线程C执行完才开始运行),这个时候我们可以通过加Flag的方式或则使用join都等可以实现,但是感觉这种方式不优雅,JDK为我们提供了两个好用的API。下面我们就来看看它们如何使用。

二、使用

1.Tread类中的方法:

1).join:

Thread:1304 Waits for this thread to die.

看源码的注释是,等待目标线程执行完成再继续执行,下面我们通过一个Demo来测试一下:

我们创建三个线程,我们在main函数中开启它们,当三个线程执行完毕后打印执行完毕的log,代码如下:

这里写图片描述
输出结果为:
这里写图片描述

并没有按我们的意图进行输出,这是因为我们开启了多线程运行,我们并不能保证哪一个线程先执行完,因此就出现了图中的结果,为了达到我们的意图,我们使用join方法对上诉代码稍加改造,如下:

这里写图片描述

我们遍历我们的线程列表,分别在主线程中调用它们的join方法达到阻塞线程的目的,运行结果如下:
这里写图片描述

这下就达到了我们的要求,join方法还有两个重载的方法,可以设定我们等待的时间,如果等待时候到了线程还未结束就往下执行,例如我们将代码中的无参join方法换成如下:

更改前:    w.join();更改后:    w.join(10);其它地方不变,程序运行结果如下:MainThread is StartThread is ready run==>Thread0Thread is ready run==>Thread1Thread is ready run==>Thread2MainThread is FinishedThread is finished==>Thread1Thread is finished==>Thread2Thread is finished==>Thread0

2).yield

Thread:267 A hint to the scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore this hint.

该方法是一个native方法,直接通过Thread.yoeld()调用,当前线程会让出CPU执行权给别的线程,这里不做讨论。

2.concurrent并发包下的方法:

1).CyclicBarrier

 * A synchronization aid that allows a set of threads to all wait for * each other to reach a common barrier point.  CyclicBarriers are * useful in programs involving a fixed sized party of threads that * must occasionally wait for each other. The barrier is called * <em>cyclic</em> because it can be re-used after the waiting threads * are released.

该类是一个同步辅助类,允许一组线程相互等待直到某个公共屏障点的到来,又因为它可以被重用,所以叫Cyclic Barrier。

看完API的介绍还是一脸懵逼,我用一个例子大家就能明白了,例如:我们运行的每一个线程都是一名短跑运动员,那么我们的CyclicBarrier就是起跑线,在开始比赛前,选手都需要在这个线上做好准备等待发令枪,然后一起跑,这个准备就是await()方法,下面我们通过例子来说明,代码如下:

这里写图片描述

可以看到我们在代码中创建了三个线程,当线程开始运行时,我们打印ready run模拟每个线程的初始化操作,然后我们调用CyclicBarrier 的await()方法,让当前线程进入等待状态,如果程序等待后就输出finished的日志,接下来我们看看在主线程中做了那些操作:

首先我们初始化 CyclicBarrier 的实例:cyclicBarrier = new CyclicBarrier(NUM, () -> System.out.println("Threads is all ready"));该类有两个构造函数: public CyclicBarrier(int parties) {     this(parties, null); } public CyclicBarrier(int parties, Runnable barrierAction) {     if (parties <= 0) throw new IllegalArgumentException();     this.parties = parties;     this.count = parties;     this.barrierCommand = barrierAction; }第一个参数的意思是我们需要等待的线程数,第二个参数是一个runable的接口,用于在等待准备好后运行,上面的代码中我们在所有线程都准备好后打印输出。

运行结果如下:
这里写图片描述

注意:如果我们其中一个线程或多个线程在准备的时候抛出异常无法进入运行到await方法,那么其余的线程会一直处于等待,为了避免这种情况我们可以为await方法添加一个超时时间,或则在catch中调用reset方法。

2).CountDownLatch

 * A synchronization aid that allows one or more threads to wait until * a set of operations being performed in other threads completes.

该类也是一个同步辅助类,该类允许一个或多个线程在一组正在其它线程运行的操作执行完后再运行。和join类似,但是使用起来更加的方便,下面我们看一段代码:
这里写图片描述

我们发现该类使用特别简单,只需要构建一个CountDownLatch实例,并在初始化时告诉它我们需要等待多少线程,当每一个线程执行完后调用countDown()方法通知计数器,当计数器到0的时候唤起等待的线程继续往下执行.

运行结果如下:MainThread is StartThread is ready run==>Thread0Thread is ready run==>Thread1Thread is ready run==>Thread2Thread is finished==>Thread0Thread is finished==>Thread2Thread is finished==>Thread1MainThread is Finished

注意:为了避免出现异常而程序无法往下执行而导致线程一直阻塞的问题,我们应当尽量设置一个等待超时时间。例如我们在代码中加入如下异常代码:

   static class WorkThread extends Thread {         WorkThread(String name) {            super(name);        }        @Override        public void run() {            System.out.println("Thread is ready run==>" + currentThread().getName());            try {                TimeUnit.SECONDS.sleep(1);                System.out.println("Thread is finished==>" + currentThread().getName());                if(currentThread().getName().contains("2")){                    throw  new InterruptedException("Something Wrong");                }                countDownLatch.countDown();            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }

当第三个线程运行时,我们抛出中断异常,运行结果如下:

这里写图片描述

这是程序无法正常结束,卡在这地方了。

3)小结

对比本节的两种方法,大致区别如下:

  1. CountDownLatch允许一个或多个线程等待一组线程执行完成,而CyclicBarrier是允许一组线程之间相互等待
  2. CountDownLatch的计数器是无法重置的,而CyclicBarrier的计数器可以被重置使用。
0 0