CyclicBarrier和CountDownLatch的使用

CyclicBarrier:

api对CyclicBarrier的描述: 一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。  也就是说他可以使一组线程先等待 然后达到某个条件之后再一起执行,有点map/reduce的感觉。

举个例子: 目前有个int,  分配3个任务线程对他加1 , 最后主任务线程汇集计算结果,代码如下:

    private static AtomicInteger i = new AtomicInteger(0);
    public static void main(String[] args){
        CyclicBarrier cb = new CyclicBarrier(3,new Runnable() {
            //主任务汇集计算结果
            public void run() {
                System.out.println("结果为" + i.get());
            }
        });
        ExecutorService es = Executors.newFixedThreadPool(5);
        es.submit(new SubTask(cb, "线程一"));
        es.submit(new SubTask(cb, "线程二"));
        es.submit(new SubTask(cb, "线程三"));
        es.shutdown();
    }
    
    //子任务计算
    private static class SubTask implements Runnable{
        private CyclicBarrier cb;
        private String msg;
        public SubTask(CyclicBarrier cb, String msg){
            this.cb = cb;
            this.msg = msg;
        }
        public void run() {
            try {
                System.out.println(msg + " enter");
                i.incrementAndGet();
                Thread.sleep(1000l);
                cb.await();
                System.out.println(msg + " quit");
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

结果:

线程一 enter
线程三 enter
线程二 enter
结果为3

线程三 quit
线程二 quit
线程一 quit

 

如果定义的参与者线程比实际的线程要少会怎么样? 比如上例中es提交4个任务结果会怎样?

api中描述:因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier 所以如果修改代码如下:

 

        CyclicBarrier cb = new CyclicBarrier(3,new Runnable() {
            //主任务汇集计算结果
            public void run() {
                System.out.println("结果为" + i.get());
            }
        });
        ExecutorService es = Executors.newFixedThreadPool(5);
        es.submit(new SubTask(cb, "线程一"));
        es.submit(new SubTask(cb, "线程二"));
        es.submit(new SubTask(cb, "线程三"));
        es.submit(new SubTask(cb, "线程四"));
        es.shutdown();                

则结果是运行完先进入的三个线程之后 第四个线程一直堵塞。

可能的输出:

线程一 enter
线程三 enter
线程二 enter
线程四 enter
结果4
线程三 quit
线程四 quit
线程一 quit

如上结果所示  有一个线程一直堵塞中

 

CountDownLatch:

api对CountDownLatch描述:一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。用给定的计数 初始化 CountDownLatch。由于调用了 countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier

由此可见: CountDownLatch与CyclicBarrier的区别是: CyclicBarrier可以重复使用  而CountDownLatch不能重复使用

简单例子如下:

    public static void main(String[] args) throws InterruptedException, BrokenBarrierException{
        //分配3个子任务去完成
        CountDownLatch cdl = new CountDownLatch(3);
        Thread t = new Thread(new SubTask(cdl, "线程1"));
        Thread t1 = new Thread(new SubTask(cdl, "线程2"));
        Thread t2 = new Thread(new SubTask(cdl, "线程3"));
        t.start();
        t1.start();
        t2.start();
        //在3个子任务完成之前一直等待
        cdl.await();
        //3个子任务完成之后  主线程获取结果
        System.out.print(i.get());
    }
    
    //子任务计算
    private static class SubTask implements Runnable{
        private CountDownLatch cb;
        private String msg;
        public SubTask(CountDownLatch cb, String msg){
            this.cb = cb;
            this.msg = msg;
        }
        public void run() {
            i.incrementAndGet();
            System.out.println(msg + "进入");
            try {
                Thread.sleep(1000l);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            cb.countDown();
        }
    }

结果:

线程2进入
线程1进入
线程3进入
3

还有一个与以上两个类使用方式非常相似的类: Semaphore

    public int MAX = 10;
    public Semaphore s = new Semaphore(MAX);

    public void semaphoreTest() throws InterruptedException{
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try{
                    s.acquire(MAX);
                    for(int i = 0; i<MAX; i++){
                        s.release();
                        Thread.sleep(1000L);
                    }
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
        });
        thread.start();
        
        Thread.sleep(1000L);
        
        while(true){
            try{
                s.acquire();
                System.out.print(1);
            }catch(Exception e){
                break;
            }
        }
    }

这个类最大的作用个人感觉就是把许可数设置为1: new Semaphore(1) 然后充当一个互斥锁。这种方式与lock比较的优势在于: lock只能有持有他的线程来释放,而semaphore实现的互斥锁可由任何线程释放,对死锁恢复非常有帮助

posted @ 2015-02-11 17:23  huliangbin  阅读(397)  评论(0编辑  收藏  举报