目录
1.概念解释
CountDownLatch概念:
CountDownLatch概念从源码分析:
CyclicBarrier概念
CyclicBarrier概念从源码分析
概念总结
2.构造器
3.代码验证二者在实际中的不同
4.归纳总结
5.网上的一些误区
CountDownLatch和CyclicBarrier都是juc下的并发工具类,二者功能在处理某些事情下看着很相似:都是阻塞线程,但是如果细品和查看源码的话会发现二者之间还是有区别的:
CountDownLatch主要是阻塞主线程,等待多线程执行完成之后再执行主线程await之后的代码片段,侧重点是主线程等待子线程(多线程)完成之后被唤醒。
CyclicBarrier主要是每个多线程在某一刻阻塞,然后各个多线程之间相互等待,直到最后一个多线程被阻塞,然后冲破栅栏,各自执行自己await()之后的代码段(另一个写法是用两个参数的构造去共完成另一个Runnable任务),侧重点是多线程之间的相互等待。
另外,CountDownLatch是一次性,而CyclicBarrier是可重复利用的(查看源码可以发现当最后一道栅栏被冲破之后,如果还需要用到的话会重新new Generation栅栏对象)。
了解了这两个类的侧重点之后,才更好选择合适并发工具类。下面就通过例子和源码,来验证这些区别。
count为计数,down减少,latch闩,所以CountDownLatch大致可以翻译为倒计时闭锁(闩)。从单词中可以很明显的得到以下信息:通过倒计时或倒数计数实现的闭锁,来实现线程的等待。
先看下该类的部分源码
/**
* Synchronization control For CountDownLatch.
* Uses AQS state to represent count.
*/
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
private final Sync sync;
源码中可以看到,CountDownLatch类中有个Sync的同步类,而Sync又继承了抽象队列同步器AbstractQueuedSynchronizer,另外看到AcquireShared又应该可以猜到是基于共享锁模式。所以说CountDownLatch是基于AQS的共享锁模式。即解释了CountDownLatch是基于AQS的共享锁模式的倒计时(数)闭锁(闩)。
cyclic循环,barrier栅栏,所以CyclicBarrier被翻译成循环栅栏。从单词中可以得到的信息是:通过栅栏的方式将一组线程阻塞,并且可以循环使用。
先看下该类的部分源码
/**
* Each use of the barrier is represented as a generation instance.
* The generation changes whenever the barrier is tripped, or
* is reset. There can be many generations associated with threads
* using the barrier - due to the non-deterministic way the lock
* may be allocated to waiting threads - but only one of these
* can be active at a time (the one to which {@code count} applies)
* and all the rest are either broken or tripped.
* There need not be an active generation if there has been a break
* but no subsequent reset.
*/
private static class Generation {
boolean broken = false;
}
/** The lock for guarding barrier entry */
private final ReentrantLock lock = new ReentrantLock();
/** Condition to wait on until tripped */
private final Condition trip = lock.newCondition();
/** The number of parties */
private final int parties;
/* The command to run when tripped */
private final Runnable barrierCommand;
/** The current generation */
private Generation generation = new Generation();
/**
* Number of parties still waiting. Counts down from parties to 0
* on each generation. It is reset to parties on each new
* generation or when broken.
*/
private int count;
源码中可以看到CyclicBarrier是通过ReentrantLock和锁的Contatin来实现的(await方法调用doawait方法,里面通过ReentrantLock实现的),然后再看ReentrantLock的部分源码
private final Sync sync;
/**
* Base of synchronization control for this lock. Subclassed
* into fair and nonfair versions below. Uses AQS state to
* represent the number of holds on the lock.
*/
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
/**
* Performs {@link Lock#lock}. The main reason for subclassing
* is to allow fast path for nonfair version.
*/
abstract void lock();
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
可以看到ReentrantLock也含有Sync类型的变量,并且还是通过CAS操作来setExclusiveOwnerThread(current)的独占锁。所以说CyclicBarrier是基于AQS的独占锁模式。
CyclicBarrier是基于AQS的独占锁模式实现的阻塞线程,CoutDownLatch是基于AQS的共享锁模式阻塞线程。
CountDownLatch只有一个构造器public CountDownLatch(int count)
CyclicBarrier有两个构造器,分别是public CyclicBarrier(int parties, Runnable barrierAction)和public CyclicBarrier(int parties)
CountDownLatch用法
为了验证先后顺序,所以开启了两个线程并且sleep的时间不同,来模拟两个业务。这样更能看出效果
import java.util.Date;
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
public static void main(String[] args) {
CountDownLatch cdl = new CountDownLatch(2);
try {
for (int i = 0; i < 2; i++) {
new Thread() {
public void run() {
long thread1Id = Thread.currentThread().getId();
try {
Date date1 = new Date();
String szDate = String.format("当前时间为:%tH时%tM分%tS秒", date1, date1, date1);
System.out.println("线程ID:" + thread1Id + "正在执行,时间:" + szDate);
int r = (int) (Math.random() * 10000);
Thread.sleep(Long.parseLong(r+""));
Date date2 = new Date();
String szDate2 = String.format("当前时间为:%tH时%tM分%tS秒", date2, date2, date2);
System.out.println("线程ID:" + thread1Id + " over" + szDate2);
cdl.countDown();
System.out.println("此时计数=" + cdl.getCount());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}.start();
}
cdl.await();
System.out.println("两个线程全部执行完成");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
执行结果:
线程ID:10正在执行,时间:当前时间为:10时43分56秒
线程ID:11正在执行,时间:当前时间为:10时43分56秒
线程ID:11 over当前时间为:10时44分03秒
此时计数=1
线程ID:10 over当前时间为:10时44分05秒
此时计数=0
两个线程全部执行完成
结果分析:
1.cdl.countDown()方法是将初始值递减。
2.主线程在调用了cdl.await();的方法之后一直在等待最后一个时间长的线程执行完成才会执行。
归纳:CountDownLatch是设置一个初始量N,每个线程中调用了countDown()方法之后,计数器变为N-1,直到计数为0的时候,调用await()的主线程才会获得锁资源执行。也就是说CountDownLatch是让一个线程(主线程)等待其他多线程全部执行完成之后再执行。
思维扩展:(感兴趣的可以自己试试)
1.在主线程调用await()的try语句块中,并且在await()之上加入一些其他的代码,会发现这些代码和两个多线程是同步执行的。也就是说只有在调用了await方法之后主线程才会被阻塞。也就是说主线程在await()下面的代码块才会被阻塞,等待获取锁。如果直接把await()方法的调用放到new CountDownLatch()之后里面执行,那惨了,后面全被阻塞了,等着去吧……
2.在多线程调用countDown()方法方法之后,再用sleep(10000),然后再sysout输出一些东西的话,会发现主线程还是会在工作时间长的多线程执行完countDown()之后会立即执行,而并不会再等10秒钟才去执行。这说明主线程cdl.getCount()等于0的时候会立马执行,不会管多线程调用countDown()之后的内容在干什么。
CyclicBarrier用法
为了验证先后顺序,所以开启了3个线程并且sleep的时间不同,来模拟三个业务。这样更能看出效果
import java.util.Date;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cb = new CyclicBarrier(3);
for (int i = 0; i < 3; i++) {
new Thread() {
public void run() {
long thread1Id = Thread.currentThread().getId();
try {
Date date1 = new Date();
String szDate = String.format("当前时间为:%tH时%tM分%tS秒", date1, date1, date1);
System.out.println("线程ID:" + thread1Id + "正在准备中,时间:" + szDate);
int r = (int) (Math.random() * 10000);
Thread.sleep(Long.parseLong(r + ""));
Date date2 = new Date();
String szDate2 = String.format("当前时间为:%tH时%tM分%tS秒", date2, date2, date2);
System.out.println("线程ID:" + thread1Id + " 准备好了" + szDate2);
try {
System.out.println("线程ID:" + thread1Id + "到达栅栏,此时前面已经阻塞了的线程数" + cb.getNumberWaiting());
cb.await();
System.out.println("线程ID:" + thread1Id + "可以开始了");
} catch (BrokenBarrierException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}.start();
}
Date date1 = new Date();
String szDate = String.format("当前时间为:%tH时%tM分%tS秒", date1, date1, date1);
System.out.println("主程序运行:" + szDate);
}
}
运行结果:
主程序运行:当前时间为:10时46分12秒
线程ID:11正在准备中,时间:当前时间为:10时46分12秒
线程ID:12正在准备中,时间:当前时间为:10时46分12秒
线程ID:10正在准备中,时间:当前时间为:10时46分12秒
线程ID:12 准备好了当前时间为:10时46分12秒
线程ID:12到达栅栏,此时前面已经阻塞了的线程数0
线程ID:10 准备好了当前时间为:10时46分16秒
线程ID:10到达栅栏,此时前面已经阻塞了的线程数1
线程ID:11 准备好了当前时间为:10时46分17秒
线程ID:11到达栅栏,此时前面已经阻塞了的线程数2
线程ID:11可以开始了
线程ID:12可以开始了
线程ID:10可以开始了
结果分析:
1.各线程到达栅栏的时候都会被阻塞,等待其他多线程也被阻塞
2.当所有的多线程都被阻塞之后会并发执行被await()之后的代码段
3.多线程和主线程是同时执行的,也就是说CyclicBarrier阻塞的是每个多线程,让多线程相互等待,直到所有的多线程都执行完await()之前的代码,与主线程无关。
归纳:
CyclicBarrier是用于多线程之间的相互等待,直到所有的多线程都到达阻塞的栅栏,然后所有多线程全部被唤醒,并发执行各自后面的代码。这个阻塞是与主线程无关的。
思维扩展:
上面已经说过了可以用CyclicBarrier的两个构造的方法,这样更方便各自执行完自己的操作后,共同去执行某个任务。
举个例子,3个程序员对产品设计的产品有意见,一个人说话产品觉得是你的问题,但是3个人一起去那就得跟他好好谈谈了。
import java.util.Date;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo2 {
public static void main(String[] args) {
CyclicBarrier cb = new CyclicBarrier(3, new Runnable() {
public void run() {
Date date1 = new Date();
String szDate = String.format("当前时间为:%tH时%tM分%tS秒", date1, date1, date1);
System.out.println("既然都到了,那就一起去找产品谈谈吧,时间:" + szDate);
}
});
for (int i = 0; i < 3; i++) {
new Thread() {
public void run() {
long thread1Id = Thread.currentThread().getId();
try {
Date date1 = new Date();
String szDate = String.format("当前时间为:%tH时%tM分%tS秒", date1, date1, date1);
System.out.println("程序员:" + thread1Id + "正在准备中,时间:" + szDate);
int r = (int) (Math.random() * 10000);
Thread.sleep(Long.parseLong(r + ""));
Date date2 = new Date();
String szDate2 = String.format("当前时间为:%tH时%tM分%tS秒", date2, date2, date2);
System.out.println("程序员:" + thread1Id + " 准备好了" + szDate2);
try {
System.out.println(
"程序员:" + thread1Id + "到达栅栏,此时前面已经来了(被阻塞了)的程序员人数(线程数)" + cb.getNumberWaiting());
cb.await();
} catch (BrokenBarrierException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}.start();
}
}
}
运行结果:
程序员:10正在准备中,时间:当前时间为:11时06分59秒
程序员:11正在准备中,时间:当前时间为:11时06分59秒
程序员:12正在准备中,时间:当前时间为:11时06分59秒
程序员:12 准备好了当前时间为:11时07分01秒
程序员:12到达栅栏,此时前面已经来了(被阻塞了)的程序员人数(线程数)0
程序员:10 准备好了当前时间为:11时07分04秒
程序员:10到达栅栏,此时前面已经来了(被阻塞了)的程序员人数(线程数)1
程序员:11 准备好了当前时间为:11时07分06秒
程序员:11到达栅栏,此时前面已经来了(被阻塞了)的程序员人数(线程数)2
既然都到了,那就一起去找产品谈谈吧,时间:当前时间为:11时07分06秒
结果可以看出,当3个程序员都准备好开战了,就去找产品交流了。
共同点:都是阻塞线程
不同点:CountDownLatch是主线程被多线程阻塞,直到多线程执行完成才被唤醒继续执行,所以更关注主线程等待多线程执行完成再继续执行的场景;CyclicBarrier是多线程各自被阻塞在栅栏前,是多线程之间的相互等待,直到全部的多线程全部执行完成,然后并发的去共同做某件事,比如:赛跑比赛的时候,需要等所有运动员都准备完成之后,才能开始进行比赛。
所以在用的时候还需要对症下药。
看过一些别的文章,CountDownLatch是通过计数器递减没毛病,但是CyclicBarrier有人说递减,也有人递增,这样就很容易误导,所以碰到这种情况,还是要自己看看源码,然后动动手测试下。所以我在这里解释下为什么对于CyclicBarrier有不同的说法,其实他们都是从自己的思路出发总结的,不能说不对。
对于CyclicBarrier,内部的原理是等待所有线程被阻塞,所以对于被阻塞的线程数来说是递增的,但是源码上有这么一段
await()方法:
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
dowait()方法:
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
lock.lock();
try {
final Generation g = generation;
if (g.broken)
throw new BrokenBarrierException();
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
int index = --count;
if (index == 0) { // tripped
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
if (command != null)
command.run();
ranAction = true;
nextGeneration();
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
// loop until tripped, broken, interrupted, or timed out
for (;;) {
try {
if (!timed)
trip.await();
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
if (g == generation && ! g.broken) {
breakBarrier();
throw ie;
} else {
// We're about to finish waiting even if we had not
// been interrupted, so this interrupt is deemed to
// "belong" to subsequent execution.
Thread.currentThread().interrupt();
}
}
if (g.broken)
throw new BrokenBarrierException();
if (g != generation)
return index;
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}
意思是说每当有线程到达后,剩余没有起作用的栅栏数int index = --count(也就是说可用栅栏数执行了--操作);所以说递减也没毛病,针对当前栅栏数是递减的。
所以这里就算对迷惑的朋友或许有所帮助。