Java多线程并发系列之闭锁(Latch)和栅栏(CyclicBarrier)
今天项目上遇到一个多线程任务问题,大概图文描述一下:
1.前端需要及时返回任务状态
2.后台开了一个任务线程去执行具体的业务,业务包括四个部分,四个部分全部完成才算完成
3.业务中某些耗时的或者需要多线程的任务单独又开了一个线程池
4.由于任务主线程和子任务线程池是并行运行,如何知道整个业务线程什么时候会完成?
好的,笔者起初的思想是给业务线程的每个子任务加了一个标记,另外起了一个监听线程在任务启动时开始监听所有业务标记是否已经达到完成状态,如果全部子任务标记完成,则认为任务完成。
业务实现上是完全没有问题的。只是单独开线程这个操作还是有点骚,并且得维护子任务标记。属实不太方便
这里最近看多线程时看到了门栓的用法,瞬间感觉可以应用一波套路
大致的实现就变成了对子任务线程池中的任务进行门栓锁定,当门栓开了后方可进行后续的任务进行,当最后子一个任务完成时,任务即可完成。
是不是方便很多,省了大把业务标记和任务监听线程。
好的 那么剩下的篇幅我们就来了解一下门栓的一个基本用法
1. 闭锁(Latch)
闭锁(Latch) —— 确保多个线程在完成各自事务后,才会打开继续执行后面的内容,否则一直等待。
计数器闭锁(CountDownLatch) —— 是JDK5+ 里面闭锁的一个实现,允许一个或多个线程等待某个事件的发生。CountDownLatch 有个正数的计数器,countDown(); 对计数器做减法操作,await(); 等待计数器 = 0。所有await的线程都会阻塞,直到计数器为0或者等待线程中断或者超时。
主要是两个方法:CountDown 和 Await方法分别为门栓减锁和门栓等待。
public static void main(String[] args) throws InterruptedException { // 申明,等待事件数量 5次 CountDownLatch await = new CountDownLatch(5); // 依次创建并启动处于等待状态的5个MyRunnable线程 for (int i = 1; i < 6; ++i) { new Thread(new MyRunnable(await, i)).start(); } System.out.println("等待线程开始工作......"); await.await(); System.out.println("结束!"); }
流程说明
这里顺便了解一下栅栏,与门栓类似的一个操作,虽然暂时未遇到这样的业务场景
2.栅栏(CyclicBarrier)
栅栏类似于闭锁,它能阻塞一组线程直到某个事件发生。 栅栏与闭锁的关键区别在于,所有的线程必须同时到达栅栏位置,才能继续执行。闭锁用于等待事件,而栅栏用于等待其他线程。
大致意思:某个业务需要分几个线程去做,但某些子任务线程中某个细节需要等待其他任务都完成到指定的栅栏位置后才可继续往后执行。下面这个例子摘自网上 虽然也不是很形象 可细看流程图
场景: 比如甲乙丙三人一把椅子,甲做椅子腿,乙做椅子面,丙做椅子靠背。等3人都做成后,就可以组装成椅子了。这是一种并行迭代,将一个问题分成很多子问题,当一系列的子问题都解决之后(所有子问题线程都已经await(); ),此时将栅栏打开,所有子问题线程被释放,而栅栏位置可以留着下次使用。
示例如下:
public static void main(String[] args) throws InterruptedException { // 申明,等待线程数量 3次 CyclicBarrier cyclicBarrier = new CyclicBarrier(3); // 依次创建并启动处于等待状态的3个MyRunnable2线程 new Thread(new ChairRunnable(cyclicBarrier, "椅子腿")).start(); new Thread(new ChairRunnable(cyclicBarrier, "椅子面")).start(); new Thread(new ChairRunnable(cyclicBarrier, "椅子背")).start(); }
public static class ChairRunnable implements Runnable { private final CyclicBarrier cyclicBarrier; private final String event; public ChairRunnable(CyclicBarrier cyclicBarrier, String event) { this.cyclicBarrier = cyclicBarrier; this.event = event; } public void run() { try { System.out.println("开始做【" + event + "】。"); Thread.sleep(new Random().nextInt(10000)); cyclicBarrier.await(); // 等待其他线程完成 } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } System.out.println("【" + event + "】做好了, 我们来一起组装吧!"); } }
运行结果: 开始做【椅子腿】。 开始做【椅子背】。 开始做【椅子面】。 【椅子面】做好了, 我们来一起组装吧! 【椅子腿】做好了, 我们来一起组装吧! 【椅子背】做好了, 我们来一起组装吧!
流程如下: