CountDownLatch与CyclicBarrier分析及其区别

相同点:
CountDownLatch、CyclicBarrier均在jdk1.5引入的,并且都在concurrent包(用于并发处理)下。均用于实现线程同步。

差异点:
1 CountDownLatch计数器只能使用一次。CyclicBarrier则可以调用其reset()方法进行重置多次使用(在计算错误时可重置后再计算)。
2 CountDownLatch使用countDown()+await()进行处理,需要通过countDown的次数到设置的次数,其await()才不会阻塞,往往是一个主线程中使用CountDownLatch然后控制所有子线程。CyclicBarrier的同步屏障是针对对应的子线程的,但同时设置了new CyclicBarrier(N)也需要对应N次的线程(部分主子线程)来执行await(),才能继续执行await()后面的代码。


CountDownLatch分析:
使用场景:用于多线程计算。如分开线程计算Excel中每个Sheet,最后再合并结果。

用法说明,具体用法如下两个:
CountDownLatch相当于一个闸门控制器,可通过构造方法初始化闸门的数量,然后通过countDown对一个个闸门进行开闸。使用await()进行阻塞,最后所有闸门都开启了await()就不会进行阻塞了。
CountDownLatch与CyclicBarrier分析及其区别_第1张图片
======================================>可以说是等待一组线程执行完成。
1 源码第一种例子分析如下:

public class Demo02 {
    private static final Integer N = 100000;
    @Test
    public void test1() throws Exception {
        CountDownLatch startSignal = new CountDownLatch(1); //启动信号
        CountDownLatch doneSignal = new CountDownLatch(N); //工作信号
        //创建N个线程
        for (int i = 0; i < N; ++i)
            new Thread(new Worker(startSignal, doneSignal)).start();


        System.out.println("主线程=>countDown前");
        //"启动信号"触发开闸,因为只有一个闸,停止await()方法的阻塞。
        startSignal.countDown();
        System.out.println("主线程=>await前");
        //"工作信号"等待其对应的所有闸门(即N个闸门都开闸),才会停止await()方法的阻塞。
        doneSignal.await();
        System.out.println("主线程=>await后");
    }
}


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 {
            //"启动信号"在这里阻塞,等待启动信号的所有闸门都开了(即触发对应的次数的countDown)
            startSignal.await();
            doWork();
            //"工作信号"在对应的子线程执行countDown()开一个闸门,等到所有子线程执行完就会开完N个闸门,这时候"工作信号"的await()就不会继续阻塞
            doneSignal.countDown();
        } catch (Exception ex) {
    } // return;
}


    private void doWork() {
        System.out.println(Thread.currentThread().getName());
    }
}

2 源码中第二个例子使用线程池异步执行新的线程并等待所有执行完毕后await()才不阻塞:

public void test2() throws InterruptedException {
    CountDownLatch doneSignal = new CountDownLatch(N);
    Executor e = Executors.newFixedThreadPool(2);


    //创建并异步启动并执行新的线程
    for (int i = 0; i < N; ++i)
        e.execute(new WorkerRunnable(doneSignal, i));
    //"工作信号"调用await()方法阻塞,直到所有的闸门开启才停止阻塞。
    doneSignal.await();
    System.out.println("执行完毕");
}
class WorkerRunnable {
.............
    public void run() {
        try {
            doWork(i);
            //线程进入线程池,异步执行,每执行完对应的逻辑,会将对应砸门(countDown())开启
            doneSignal.countDown();
        } catch (Exception ex) {
        } // return;
    }
.............
}


上面说了CountDownLatch使用的基本方法和原理,那它的锁是怎么实现的?
翻其源码可见有一个静态内部类如下:
private static final class Sync extends AbstractQueuedSynchronizer {............}

注:如上可见CountDownLatch是借助AQS实现的锁机制,具体见AQS锁的文章(https://www.cnblogs.com/waterystone/p/4920797.html),更多关于AQS的分析文章待续。


CyclicBarrier分析:
应用场景:多线程计算数据,如分开线程计算Excel中每个Sheet,最后再合并结果。

用法说明,其用法如下:
对于CountDownLatch,这个也是一个实现线程同步的类。但区别是CyclicBarrier为每个线程都设置了一个内存屏障,只有所有达到了同步屏障才能统一通过其await()方法,进入到await()后面执行的代码的操作,对应县城的await()才不会阻塞。
CountDownLatch与CyclicBarrier分析及其区别_第2张图片
======================================>可以说是一组线程相互等待。

实例用法如下:

private static CyclicBarrier cyclicBarrier = new CyclicBarrier(5);  //设置五个同步屏障,即需要五个线程都执行了await()才允许通过


@Test
public void test3() throws Exception {
    for (int i = 0; i < 3; i++) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println(Thread.currentThread().getName() + ",停止当前线程内存屏障前");//①
                    cyclicBarrier.await(); //解除当前子线程屏障
                    System.out.println(Thread.currentThread().getName() + ",停止当前线程内存屏障后");//②
                } catch (Exception ignored) {}
            }
        }).start();
    }
    //停止掉主线程的一个内存屏障
    System.out.println(Thread.currentThread().getName() + ",停止主线程内存屏障前");//④
    cyclicBarrier.await();
    System.out.println(Thread.currentThread().getName() + ",停止主线程内存屏障后");//⑥
    System.out.println("执行完毕");
}

注:以上操作是永远都不可能执行到②⑥,因为for里面只执行了三个线程对应通过了三个同步屏障,主线程执行了一次await()通过一个同步屏障,加起来<定义的五个同步屏障。所以所有线程的await()后面的代码均不可继续执行,阻塞在那里了。(解决办法:将new CyclicBarrier(5)改为new CyclicBarrier(4)这样同步屏障数量就刚好跟线程执行await()的数量对上了)。

参考:http://ifeve.com/concurrency-cyclicbarrier/
 

你可能感兴趣的:(JAVA)