1、CountDownLatch和CyclicBarrier作用
CountDownLatch和CyclicBarrier都位于java.util.concurrent包中,都具有计数功能,一般用于多线程间的协作。
CountDownLatch是减法计数器,子线程中调用countDown()计数减1,主线程调用await()阻塞线程执行,直到计数为0才会让线程继续执行,CountDownLatch不可重复利用。
CyclicBarrier是加法计数器,子线程调用await()计数加1,同时阻塞线程执行,直到兄弟线程都执行了await(),计数到达了设定值,CyclicBarrier可重复使用,计数到达指定值后重新开始。CyclicBarrier构造函数设定一个Runnable参数,通知计数已到达指定值。
2、使用示例
package com.zhi.test; import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; import org.junit.Test; /** * CyclicBarrier和CountDownLatch使用样例 * * @author 张远志 * @since 2020年5月4日18:01:00 * */ public class MyTest { @Test public void test() { int jobCount = 5; CountDownLatch latch = new CountDownLatch(jobCount); CyclicBarrier barrier = new CyclicBarrier(jobCount, new Runnable() { public void run() { System.out.println("所有线程都到了"); } }); for (int i = 1; i <= jobCount; i++) { new Thread(new Job(latch, barrier, i)).start(); } try { latch.await(); } catch (Exception e) { } System.out.println("所有线程已执行完成!"); } class Job implements Runnable { private CountDownLatch latch; private CyclicBarrier barrier; private final int id; public Job(CountDownLatch latch, CyclicBarrier barrier, int id) { this.latch = latch; this.barrier = barrier; this.id = id; } @Override public void run() { try { Thread.sleep(new Random().nextInt(10) * 1000); System.out.println("线程" + id + "到达栅栏1"); barrier.await(); } catch (Exception e) { } try { System.out.println("线程" + id + "到达栅栏2"); barrier.await(); } catch (Exception e) { } System.out.println("任务" + id + "执行完成"); latch.countDown(); } } }
执行结果:
线程2到达栅栏1 线程4到达栅栏1 线程5到达栅栏1 线程3到达栅栏1 线程1到达栅栏1 所有线程都到了 线程1到达栅栏2 线程2到达栅栏2 线程5到达栅栏2 线程4到达栅栏2 线程3到达栅栏2 所有线程都到了 任务3执行完成 任务1执行完成 任务2执行完成 任务4执行完成 任务5执行完成 所有线程已执行完成!
3、如何保证计数的线程安全性
CountDownLatch的countDown会进行加法计数,CyclicBarrier的await()会进行加法计数,查看源码我们会发现,这2个方法都没有使用synchronized关键字,那他们有时如何保证线程安全性的呢?
查看CountDownLatch源码,我们看到CountDownLatch使用了Sync内部类(继承AbstractQueuedSynchronizer)做减法,通过Unsafe类比较并设置新值。
查看CyclicBarrier源码,我们看到ReentrantLock锁。为啥它与CountDownLatch不一样呢,因为CyclicBarrier不仅要完成计数,还需要调用barrierCommand,并且要实现重复可用(将count值还原),其实ReentrantLock内部也使用Unsafe类进行了原子操作。