Java 并发同步器之CountDownLatch、CyclicBarrier

一、简介                                                                                                 

   1、CountDownLatch是一个同步计数器,构造时传入int参数,该参数就是计数器的初始值,每调用一次countDown()方法,计数器减1,计数器大于0 时,await()方法会阻塞程序的执行。 CountDownLatch可以看作是一个倒计数的锁存器,当计数减至0时触发特定的事件。利用这种特性,可以让主线程等待子线程的结束。

CountDownLatch最重要的方法是countDown()和await(),前者主要是倒数一次,后者是等待倒数到0,如果没有到达0,就只有阻塞等待了。

   2、CyclicBarrier也是一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。wait() 方法每被调用一次,计数便会减少1,并阻塞住当前线程。当计数减至0时,阻塞解除,所有在此 CyclicBarrier上面阻塞的线程开始运行。在这之后,如果再次调用 await() 方法,计数就又会变成 N-1,新一轮重新开始,所以称它为循环的 barrier。

二、使用场景                                                                                            

   1、CountDownLatch可用于一组线程和另外一组线程的协作。比如有一个任务想要往下执行,但必须要等到其他的任务执行完毕后才可以继续往下执行。假如我们这个想要继续往下执行的任务调用一个CountDownLatch对象的await()方法,其他的任务执行完自己的任务后调用同一个CountDownLatch对象上的countDown()方法,那这个调用await()方法的任务将一直阻塞等待,直到这个CountDownLatch对象的计数值减到0为止。

   2、CyclicBarrier需要所有的子任务都完成时,才执行主任务,这个时候就可以选择使用CyclicBarrier。

CountDownLatch 适用于一组线程和另一个主线程之间的工作协作。一个主线程等待一组工作线程的任务完毕才继续它的执行是使用 CountDownLatch 的主要场景;CyclicBarrier 用于一组或几组线程,比如一组线程需要在一个时间点上达成一致,例如同时开始一个工作。另外,CyclicBarrier 的循环特性和构造函数所接受的 Runnable 参数也是 CountDownLatch 所不具备的。

三、常用方法                                                                                            

CountDownLatch 
1、public void countDown()
递减锁存器的计数,如果计数到达零,则释放所有等待的线程。如果当前计数大于零,则将计数减少。如果新的计数为零,出于线程调度目的,将重新启用所有的等待线程。
如果当前计数等于零,则不发生任何操作。

2、public boolean await(long timeout, TimeUnit unit) throws InterruptedException
使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。如果当前计数为零,则此方法立刻返回 true 值。
如果当前计数大于零,则出于线程调度目的,将禁用当前线程,且在发生以下三种情况之一前,该线程将一直处于休眠状态:
由于调用 countDown() 方法,计数到达零;或者
其他某个线程中断当前线程;或者
已超出指定的等待时间。
如果计数到达零,则该方法返回 true 值。
如果当前线程:
在进入此方法时已经设置了该线程的中断状态;或者
在等待时被中断,
则抛出 InterruptedException,并且清除当前线程的已中断状态。如果超出了指定的等待时间,则返回值为 false。如果该时间小于等于零,则此方法根本不会等待。
 
参数:
timeout - 要等待的最长时间
unit - timeout 参数的时间单位。
返回:
如果计数到达零,则返回 true;如果在计数到达零之前超过了等待时间,则返回 false
抛出:
InterruptedException - 如果当前线程在等待时被中断
CyclicBarrier 
public int await() throws InterruptedException, BrokenBarrierException
在所有参与者都已经在此 barrier 上调用 await方法之前,将一直等待。如果当前线程不是将到达的最后一个线程,出于调度目的,将禁用它,且在发生以下情况之一前,该线程将一直处于休眠状态:
最后一个线程到达;或者
其他某个线程中断当前线程;或者
其他某个线程中断另一个等待线程;或者
其他某个线程在等待 barrier 时超时;或者
其他某个线程在此 barrier 上调用 reset()。
如果当前线程:
在进入此方法时已经设置了该线程的中断状态;或者
在等待时被中断
则抛出 InterruptedException,并且清除当前线程的已中断状态。如果在线程处于等待状态时 barrier 被 reset(),或者在调用 await 时 barrier 被损坏,抑或任意一个线程正处于等待状态,则抛出 BrokenBarrierException 异常。
如果任何线程在等待时被 中断,则其他所有等待线程都将抛出 BrokenBarrierException 异常,并将 barrier 置于损坏状态。
如果当前线程是最后一个将要到达的线程,并且构造方法中提供了一个非空的屏障操作,则在允许其他线程继续运行之前,当前线程将运行该操作。如果在执行屏障操作过程中发生异常,则该异常将传播到当前线程中,并将 barrier 置于损坏状态。
 
返回:
到达的当前线程的索引,其中,索引 getParties() - 1 指示将到达的第一个线程,零指示最后一个到达的线程
抛出:
InterruptedException - 如果当前线程在等待时被中断
BrokenBarrierException - 如果另一个 线程在当前线程等待时被中断或超时,或者重置了 barrier,或者在调用 await 时 barrier 被损坏,抑或由于异常而导致屏障操作(如果存在)失败。

四、示例                                                                                             

   我们看下JDK文档中的示例

1、CountDownLatch 

我们可以想象下赛车上的情景赛车的维修过程,只有 startSignal命令下达之后,维修工才开始干活,只有等所有工人完成工作之后,赛车才能继续。

class Driver { // ...
    void main() throws InterruptedException {
        CountDownLatch startSignal = new CountDownLatch(1);
        CountDownLatch doneSignal = new CountDownLatch(N);

        for (int i = 0; i < N; ++i) // create and start threads
            new Thread(new Worker(startSignal, doneSignal)).start();

        doSomethingElse();            // don't let run yet
        startSignal.countDown();      // let all threads proceed
        doSomethingElse();
        doneSignal.await();           // wait for all to finish
    }
}

class Worker implements Runnable {
    private final CountDownLatch startSignal;
    private final CountDownLatch doneSignal;
    Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
        this.startSignal = startSignal;
        this.doneSignal = doneSignal;
    }
    public void run() {
        try {
            startSignal.await();
            doWork();
            doneSignal.countDown();
        } catch (InterruptedException ex) {} // return;
    }

    void doWork() { ... }
}

 

2、CyclicBarrier 

class Solver {
    final int N;
    final float[][] data;
    final CyclicBarrier barrier;

    class Worker implements Runnable {
        int myRow;
        Worker(int row) { myRow = row; }
        public void run() {
            while (!done()) {
                processRow(myRow);

                try {
                    barrier.await();
                } catch (InterruptedException ex) {
                    return;
                } catch (BrokenBarrierException ex) {
                    return;
                }
            }
        }
    }

    public Solver(float[][] matrix) {
        data = matrix;
        N = matrix.length;
        barrier = new CyclicBarrier(N, new Runnable() {
                public void run() {
                    mergeRows(...);
                }
            });
        for (int i = 0; i < N; ++i)
            new Thread(new Worker(i)).start();

        waitUntilDone();
    }
}

 

 五、总结                                                                                              

   CountDownLatch 是能使一组线程等另一组线程都跑完了再继续跑;

 CyclicBarrier 能够使一组线程在一个时间点上达到同步,可以是一起开始执行全部任务或者一部分任务。同时,它是可以循环使用的。

 

参考:http://my.oschina.net/lifany/blog/207995?p=2#comments 

 

由于本人经验有限,文章中难免会有错误,请浏览文章的您指正或有不同的观点共同探讨!

posted @ 2015-09-20 22:25  三石雨  阅读(828)  评论(0编辑  收藏  举报