java并发编程之CountDownLatch与CyclicBa

作者: miaoLoveCode | 来源:发表于2017-01-23 12:35 被阅读2112次

    CountDownLatch和CyclicBarrier是jdk concurrent包下非常有用的两个并发工具类,它们提供了一种控制并发流程的手段。本文将会提供一些应用场景,结合源码,对它们的具体实现以及如何使用做一个具体分析。

    CountDownLatch

    CountDownLatch允许一个或多个线程等待其他线程完成操作。

    CountDownLatch使用案例

    需求:解析一个文件下多个txt文件数据,可以考虑使用多线程并行解析以提高解析效率。每一个线程解析一个文件里的数据,等到所有数据解析完毕之后再进行其他操作。

    设计分析:在这个需求中,需要实现主线程等待所有线程完成文件解析操作,CountDownLatch正好可以做到。

    代码实现

    注:只给一个简单的架子,表明CountDownLatch的具体使用方式,txt文件解析在这里就不做详细的代码实现了。

    CountDownLatch使用示例
    • CountDownLatch声明:
      CountDownLatch的构造函数接受int型参数作为它的计数器,如果想等待N个点完成,就传入N;
    • 调用CountDownLatch的countDown方法时,N会减1,CountDownLatch的await方法会阻塞主线程直到N减少到0。
    CountDownLatch源码分析

    CountDownLatch是自定义AQS同步组件,接下来就以自定义同步器Sync、countDown方法和await方法为切入点,分析CountDownLatch的具体实现。

    • 自定义同步器Sync实现


      自定义同步器实现

      同步器Sync实现了共享式获取同步状态的acquire和release,前文中已经详细介绍过AQS相关内容,在这里我就不再做详细介绍分析了。

    • 构造方法


      构造方法

      从构造方法的具体实现可以看出,通过构造方法传入的int型参数count其实就是同步器的状态。

    • countDown实现


      countDown实现

      整个countDown只做了一件事情,释放同步状态,同步状态在这里的实际意义也就是需要等待的完成的点的数量,只要每完成一个点,就调用countDown方法释放同步状态。

    • await实现
      CountDownLatch提供带超时时间的await和不带超时时间的await:


      await实现

      await的实质是在获取同步状态,同步状态state == 0成立,当前等待完成的点均已完成,主线程继续往下执行,否则,主线程进入等待队列自旋等待直到同步状态释放后state == 0。有些时候主线程是不能一直自旋等待,这个时候带超时时间的await就派上用场了,设置超时时间,如果在指定时间内N个点都未完成,返回false,主线程不再等待,继续往下执行。

    总结:CountDownLatch实质上就是一个AQS计数器,通过AQS来实现线程的等待与唤醒。

    CyclicBarrier

    CyclicBarrier,让一组线程到达一个同步点后再一起继续运行,在其中任意一个线程未达到同步点,其他到达的线程均会被阻塞。

    CyclicBarrier源码分析
    • 构造方法
      CyclicBarrier提供两个构造方法CyclicBarrier(int parties)CyclicBarrier(int parties, Runnable barrierAction)
      CyclicBarrier构造方法
      • CyclicBarrier(int parties)
        默认构造方法,参数表示拦截的线程数量。

      • CyclicBarrier(int parties, Runnable barrierAction)
        由于线程之前的调度是由CPU决定的,所以默认的构造方法无法设置线程执行优先级,CyclicBarrier提供一个更高级的构造函数CyclicBarrier(int parties, Runnable barrierAction),用于在线程到达同步点时,优先执行线程barrierAction,这样可以更加方便的处理一些负责的业务场景。

    创建CyclicBarrier后,每个线程调用await方法告诉CyclicBarrier自己已经到达同步点,然后当前线程被阻塞。接下来我们来看看await方法的具体实现。

    • await实现
      CyclicBarrier同样提供带超时时间的await和不带超时时间的await:


      await实现

      整个await方法的核心是dowait方法的调用,我们来看看dowait的具体实现。

    • dowait实现

      1. 在dowait的前段部分,主要完成了当所有线程都到达同步点(barrier)时,唤醒所有的等待线程,一起往下继续运行,可根据参数barrierAction决定优先执行的线程。


        dowait实现前半部分
      2. 在dowait的实现后半部分,主要实现了线程未到达同步点(barrier)时,线程进入Condition自旋等待,直到等待超时或者所有线程都到达barrier时被唤醒。

      dowait实现后半部分

      在整个dowait:

      1. 使用ReentrantLock保证每一次操作线程安全;
      2. 线程等待/唤醒使用Lock配合Condition来实现;
      3. 线程被唤醒的条件:等待超时或者所有线程都到达barrier。

    到这里为止,CyclicBarrier的重要实现源码分析就结束了,接下来还是照样给出一个具体的使用案例,方便掌握CyclicBarrier的具体用法。

    CyclicBarrier使用案例

    需求:多线程计算数据,merge计算结果。

    代码实现

    使用案例

    运行结果:

    运行结果

    CyclicBarrier和CountDownLatch都可以实现线程等待,那么它俩之间的区别是什么呢?

    CyclicBarrier和CountDownLatch的区别

    看了各种资料和书,大家一致的意见都是CountDownLatch是计数器,只能使用一次,而CyclicBarrier的计数器提供reset功能,可以多次使用。但是我不那么认为它们之间的区别仅仅就是这么简单的一点。我们来从jdk作者设计的目的来看,javadoc是这么描述它们的:

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

    CyclicBarrier : A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point.

    从javadoc的描述可以得出:

    • CountDownLatch:一个或者多个线程,等待其他多个线程完成某件事情之后才能执行;
    • CyclicBarrier:多个线程互相等待,直到到达同一个同步点,再继续一起执行。

    对于CountDownLatch来说,重点是“一个线程(多个线程)等待”,而其他的N个线程在完成“某件事情”之后,可以终止,也可以等待。而对于CyclicBarrier,重点是多个线程,在任意一个线程没有完成,所有的线程都必须等待。

    CountDownLatch是计数器,线程完成一个记录一个,只不过计数不是递增而是递减,而CyclicBarrier更像是一个阀门,需要所有线程都到达,阀门才能打开,然后继续执行。

    相关文章

      网友评论

      • 平安_72f2:想在所有线程写入操作完后,进行额外操作可以为CyclicBarrier提供Runnable参数,这个可以帮助理解上面的程序
      • 996d80a96bca:jdk并发包你怎么学习的?

      本文标题:java并发编程之CountDownLatch与CyclicBa

      本文链接:https://www.haomeiwen.com/subject/zyvwbttx.html