CountDownLatch(倒计时器)
定义:
CountDownLatch
允许count
个线程阻塞在一个地方,直至所有线程的任务都执行完毕。CountDownLatch
是共享锁的一种实现,它默认构造 AQS 的state
值为count
。当线程使用
countDown()
方法时,其实使用了tryReleaseShared
方法以 CAS 的操作来减少state
,直至state
为 0 。当调用await()
方法的时候,如果state
不为 0,那就证明任务还没有执行完毕,await()
方法就会一直阻塞,也就是说await()
方法之后的语句不会被执行。然后,CountDownLatch
会自旋 CAS 判断state == 0
,如果state == 0
的话,就会释放所有等待的线程,await()
方法之后的语句得到执行。注意:计数器必须大于等于0,只是等于0时候,计数器就是零,调用await方法时不会阻塞当前线程。CountDownLatch不可能重新初始化或者修改CountDownLatch对象的内部计数器的值。一个线程调用countDown方法happen-before,另外一个线程调用await方法。CountDownLatch 的两种典型用法
某一线程在开始运行前等待 n 个线程执行完毕。将 CountDownLatch 的计数器初始化为 n :
new CountDownLatch(n)
,每当一个任务线程执行完毕,就将计数器减 1countdownlatch.countDown()
,当计数器的值变为 0 时,在CountDownLatch上 await()
的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。做法是初始化一个共享的
CountDownLatch
对象,将其计数器初始化为 1 :new CountDownLatch(1)
,多个线程在开始执行任务前首先coundownlatch.await()
,当主线程调用 countDown() 时,计数器变为 0,多个线程同时被唤醒。CountDownLatch 的使用示例
实例1
public class CountDownLatchExample1 { // 请求的数量 private static final int threadCount = 550; public static void main(String[] args) throws InterruptedException { // 创建一个具有固定线程数量的线程池对象(如果这里线程池的线程数量给太少的话你会发现执行的很慢) ExecutorService threadPool = Executors.newFixedThreadPool(300); final CountDownLatch countDownLatch = new CountDownLatch(threadCount); for (int i = 0; i < threadCount; i++) { final int threadnum = i; threadPool.execute(() -> {// Lambda 表达式的运用 try { test(threadnum); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { countDownLatch.countDown();// 表示一个请求已经被完成 } }); } countDownLatch.await(); threadPool.shutdown(); System.out.println("finish"); } public static void test(int threadnum) throws InterruptedException { Thread.sleep(1000);// 模拟请求的耗时操作 System.out.println("threadnum:" + threadnum); Thread.sleep(1000);// 模拟请求的耗时操作 } }
解释:
上边的代码将CountDownLatch代码设置成550,也就是说当执countDownLatch.await();的时候,需要执行550次的countDownLatch.countDown();,所以在当前线程上使用await,需要等在主线程创建的线程都执行完并且countDown完成后,才可以继续执行await后面的代码
实例2
public class CountDownLatchTest { staticCountDownLatch c = new CountDownLatch(2); public static void main(String[] args) throws InterruptedException { new Thread(new Runnable() { @Override public void run() { System.out.println(1); c.countDown(); System.out.println(2); c.countDown(); } }).start(); c.await(); System.out.println("3"); }
解释:
上边的代码将CountDownLatch代码设置成2,也就是说当执countDownLatch.await();的时候,需要执行2次的countDownLatch.countDown();,当前线程上创建了一个子线程,子线程中进行2次countDown,也就是说只有当子线程全部执行完后才能执行后续的输出
只能是这个顺序(1->2->3),当子线程输出完1的时候准备输出2的时候,只进行了1次的countDown操作,所以当前线程还需要继续等待
那么重点来了:
CountDownLatch 是一次性的,计数器的值只能在构造方法中初始化一次,之后没有任何机制再次对其设置值,当 CountDownLatch 使用完毕后,它不能再次被使用。
CyclicBarrier(循环栅栏)
定义:
CyclicBarrier 和 CountDownLatch 非常类似,它也可以实现线程间的技术等待,但是它的功能比 CountDownLatch 更加复杂和强大。主要应用场景和 CountDownLatch 类似。
CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。CyclicBarrier 默认的构造方法是
CyclicBarrier(int parties)
,其参数表示屏障拦截的线程数量,每个线程调用await
方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。通俗的解释就是:每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。当await的次数到达创建CyclicBarrier规定的次数的时候一同放开
代码实例
public class CyclicBarrierExample3 { // 请求的数量 private static final int threadCount = 550; // 需要同步的线程数量 private static final CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> { System.out.println("------当线程数达到之后,优先执行------"); }); public static void main(String[] args) throws InterruptedException { // 创建线程池 ExecutorService threadPool = Executors.newFixedThreadPool(10); for (int i = 0; i < threadCount; i++) { final int threadNum = i; Thread.sleep(1000); threadPool.execute(() -> { try { test(threadNum); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (BrokenBarrierException e) { // TODO Auto-generated catch block e.printStackTrace(); } }); } threadPool.shutdown(); } public static void test(int threadnum) throws InterruptedException, BrokenBarrierException { System.out.println("threadnum:" + threadnum + "is ready"); cyclicBarrier.await(); System.out.println("threadnum:" + threadnum + "is finish"); } }
解释:
首先CyclicBarrier 还提供一个更高级的构造函数
CyclicBarrier(int parties, Runnable barrierAction)
,用于在线程到达屏障时,优先执行barrierAction
,方便处理更复杂的业务场景。 也就是我们那句CyclicBarrier(int parties, Runnable barrierAction)
,也就是说当await到达次数的时候会优先执行barrierAction线程,然后再去执行请求的线程
上边的代码是5个线程一同执行,并且可以循环执行,这个效果是
CountDownLatch不能实现的,CountDownLatch是计数的只能执行1次重点: 同时CyclicBarrier还可以使用reset()重置次数,例如定义CyclicBarrier为5,此时已经执行了3次await,此时出现了计算错误需要重新计算,那么即可使用reset()重置次数
CyclicBarrier 和 CountDownLatch 的区别
CountDownLatch 是计数器,只能使用一次,而 CyclicBarrier 的计数器提供 reset 功能,可以多次使用。但是我不那么认为它们之间的区别仅仅就是这么简单的一点。
对于 CountDownLatch 来说,重点是“一个线程(多个线程)等待”,而其他的 N 个线程在完成“某件事情”之后,可以终止,也可以等待。而对于 CyclicBarrier,重点是多个线程,在任意一个线程没有完成,所有的线程都必须等待。
CountDownLatch 是计数器,线程完成一个记录一个,只不过计数不是递增而是递减,而 CyclicBarrier 更像是一个阀门,需要所有线程都到达,阀门才能打开,然后继续执行。