JAVA并发包中有三个类用于同步一批线程的行为,分别是闭锁(Latch),信号灯(Semaphore)和栅栏(CyclicBarrier)。本贴主要说明闭锁(Latch)和栅栏(CyclicBarrier)。
1. 闭锁(Latch)
闭锁(Latch) —— 确保多个线程在完成各自事务后,才会打开继续执行后面的内容,否则一直等待。
计数器闭锁(CountDownLatch) —— 是JDK5+ 里面闭锁的一个实现,允许一个或多个线程等待某个事件的发生。CountDownLatch 有个正数的计数器,countDown(); 对计数器做减法操作,await(); 等待计数器 = 0。所有await的线程都会阻塞,直到计数器为0或者等待线程中断或者超时。
1 public static void main(String[] args) throws InterruptedException { 2 // 申明,等待事件数量 5次 3 CountDownLatch await = new CountDownLatch(5); 4 5 // 依次创建并启动处于等待状态的5个MyRunnable线程 6 for (int i = 1; i < 6; ++i) { 7 new Thread(new MyRunnable(await, i)).start(); 8 } 9 10 System.out.println("等待线程开始工作......"); 11 await.await(); 12 System.out.println("结束!"); 13 }
1 public static class MyRunnable implements Runnable { 2 3 private final CountDownLatch await; 4 private final int num; 5 6 public MyRunnable(CountDownLatch await, int num) { 7 this.await = await; 8 this.num = num; 9 } 10 11 public void run() { 12 try { 13 System.out.println("线程"+num+"执行完毕。"); 14 await.countDown(); // 当前事件执行完毕,计数 -1 15 } catch (InterruptedException e) { 16 e.printStackTrace(); 17 } 18 } 19 }
运行结果: 等待线程开始工作...... 线程1执行完毕。 线程2执行完毕。 线程3执行完毕。 线程4执行完毕。 线程5执行完毕。 结束!
流程如图所示:
图1 - CountDownLatch 处理流程
2. 栅栏(CyclicBarrier)
栅栏类似于闭锁,它能阻塞一组线程直到某个事件发生。 栅栏与闭锁的关键区别在于,所有的线程必须同时到达栅栏位置,才能继续执行。闭锁用于等待事件,而栅栏用于等待其他线程。
场景: 比如甲乙丙三人一把椅子,甲做椅子腿,乙做椅子面,丙做椅子靠背。等3人都做成后,就可以组装成椅子了。这是一种并行迭代,将一个问题分成很多子问题,当一系列的子问题都解决之后(所有子问题线程都已经await(); ),此时将栅栏打开,所有子问题线程被释放,而栅栏位置可以留着下次使用。
示例如下:
1 public static void main(String[] args) throws InterruptedException { 2 // 申明,等待线程数量 3次 3 CyclicBarrier cyclicBarrier = new CyclicBarrier(3); 4 5 // 依次创建并启动处于等待状态的3个MyRunnable2线程 6 new Thread(new ChairRunnable(cyclicBarrier, "椅子腿")).start(); 7 new Thread(new ChairRunnable(cyclicBarrier, "椅子面")).start(); 8 new Thread(new ChairRunnable(cyclicBarrier, "椅子背")).start(); 9 }
1 public static class ChairRunnable implements Runnable { 2 private final CyclicBarrier cyclicBarrier; 3 private final String event; 4 5 public ChairRunnable(CyclicBarrier cyclicBarrier, String event) { 6 this.cyclicBarrier = cyclicBarrier; 7 this.event = event; 8 } 9 10 public void run() { 11 try { 12 System.out.println("开始做【" + event + "】。"); 13 Thread.sleep(new Random().nextInt(10000)); 14 cyclicBarrier.await(); // 等待其他线程完成 15 } catch (InterruptedException e) { 16 e.printStackTrace(); 17 } catch (BrokenBarrierException e) { 18 e.printStackTrace(); 19 } 20 System.out.println("【" + event + "】做好了, 我们来一起组装吧!"); 21 } 22 }
运行结果: 开始做【椅子腿】。 开始做【椅子背】。 开始做【椅子面】。 【椅子面】做好了, 我们来一起组装吧! 【椅子腿】做好了, 我们来一起组装吧! 【椅子背】做好了, 我们来一起组装吧!
流程如下图所示:
图2 - CyclicBarrier 处理流程