CountDownLatch是基于AQS队列同步器实现的共享锁。CyclicBarrier是基于同步/等待队列实现。
CountDownLatch是单纯通过维持一个state的计数数值,这个数值在线程调用countDownLatch.countDown()的时候会减1,当计数器的值为零的时候,等待的线程会向前执行。假如我们需要保证在主线程继续往前执行的时候,子任务都已经被启动,可以通过CountDownLatch来进行控制。实际上和ReetrantLock是基于同样的原理实现,不过是实现和覆盖了tryAccquireShare和tryReleaseShare的区别。
CyclicBarrier则和CountDownLatch不相同,它是通过Lock和Condition等待/通知机制实现的。在CountDownLatch实现的多线程模型中,各个线程之间是通过同步状态进行通信。CyclicBarrier通过lock+Condition来维持一个同步队列和等待队列(通常将等待在同步块外面的队列称之为同步队列,将等待在某个对象上的队列称之为等待队列),Lock中的Condition对象可以有多个,也就是维护多个等待队列。我们观察CyclicBarrier的实现可以看到它的内部只维持了一个等待队列。
1 | /** The lock for guarding barrier entry */ |
CountDownLatch的使用
我们通过一段代码查看CountDownLatch的主要使用方式,我们定义一个woker线程,作为子任务:
1 | package CountDownLatch; |
通过main方法调用:
1 | package CountDownLatch; |
输出的结果:
1 | worker_1释放一次共享锁 |
这样的使用方式我们可以看到CountDownLatch实际上保证了当前主线程会等待所有的线程都启动,我们可以通过countDownLatch.countDown()这个方法去动态更改state的数值,主线程只有在state的数值变成0之后才会选择继续向前执行。这样的处理似乎很像thread.join这种等待当前线程执行结束在执行主线程的方式,不同的是使用CountDownLatch你不一定要保证线程已经执行结束进入死亡状态,你可以在线程执行栈中通过countDownLatch.countDown()手动更改这个state的数值,只要这个数值为0,主线程便可以开始运行。
CountDownLatch源代码分析
CountDownLatch实现的几个主要方法,通过内部的sync重写了AQS队列同步器中的获取和释放共享锁的方法。我们通过CountDownLatch的构造方法去设置state的数值。getCount可以获得当前的state值。通过countDown去循环cas减少state的数值,最后的await方法实现循环等待state的值,直到变为零。这里看下await方法的主要源码:
1 | /** |
CyclicBarrier的使用
对比CountDownLatch的使用我们可以发现CountDownLatch的缺陷是线程是完全互不干扰的,也就是没有相互的协同工作。CyclicBarrier通过lock维护一个同步队列,再通过locks.newCondition获取到的condition对象维护一个等待队列,形成等待/通知机制,conditon的await和signal类似于object的wait和notify。
1 | package CyclicBarrier; |
main函数启动:
1 | package CyclicBarrier; |
输出结果:
1 | 5个用户开始抽奖Wed Apr 04 14:31:18 CST 2018 |
CyclicBarrier源码分析
这里主要分析cyclicBarrier.await方法:
1 | /** |
整个执行的过程,线程在调用了await方法之后,会通过lock方式上锁,然后线程将计数器的值减1。如果计数器的值到达0,说明conditon维护的等待队列已满,调用nextGeneration唤醒condition等待队列上的所有线程。加入到同步队列中,然后返回index,释放锁,之后同步队列中的节点线程就可以被唤醒了。如果线程的计数器没有到达0,那更简单,这个时候其他线程会调用condition的await方法进入等待队列。
总结
这里总结下两者的主要区别:
使用方式:CountDownLatch使用时主线程等待子线程将同步状态置为0,CyclicBarrier所有线程在等待队列上等待被通知。
实现方式:CountDownLatch底层使用的是共享锁,CyclicBarrier底层使用的是ReentrantLock和这个lock的条件对象Condition,通过等待通知机制(同步队列+等待队列)实现。
主要用途:CountDownLatch用完之后就不能再次使用了,CyclicBarrier用完之后可以通过reset再次使用。例如在多线程计算的时候如果失败了,CyclicBarrier可以重新计算。
API:CyclicBarrier可以通过isBroken方法查看阻塞的线程是否被中断。