开始磨刀霍霍向多线程了,这期是 CountDownLatch 的一个小示例。
定义:CountDownLatch 允许一个或多个线程等待其他线程完成操作。
应用需求举例:假设有4个线程,A、B、C、D,线程 D 需要在 A、B、C 执行完之后再执行。
应用需求分析:如上描述,如果想让线程 D 最后执行,结合之前的学习,我们可以采用 join() 方法来实现,比如在 A 线程调 B.join(),B 线程调用 C.join() 等等,我们来回忆一下 join() 方法:一个线程调用另一个线程的 join() 方法时,当前线程阻塞,等待被调用 join() 方法的线程执行完毕才能继续执行,这样可以保证线程执行顺序。
如下为 join() 方法实现:
public class Demo { public static void main(String[] args) { Thread A = new Thread(new Runnable() { @Override public void run() { System.out.println("A"); } }); Thread B = new Thread(new Runnable() { @Override public void run() { System.out.println("B 开始等待 A"); try { A.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("B"); } }); Thread C = new Thread(new Runnable() { @Override public void run() { System.out.println("C 开始等待 B"); try { B.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("C"); } }); Thread D = new Thread(new Runnable() { @Override public void run() { System.out.println("D 开始等待 C"); try { C.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("D"); } }); B.start(); A.start(); C.start(); D.start(); } }
执行结果如下:
B 开始等待 A C 开始等待 B A D 开始等待 C B C D
显然 join() 方法是可以完成 D 线程最后执行的要求,但是却失去了多线程并行执行的意义,怎么讲?
因为线程 A、B、C 无法完成同步运行,本质上还是串行,join() 方法其内部的实现是基于等待通知机制。
为了解决这个问题,我们可以利用 CountdownLatch 来实现这类通信方式,用法如下:
- CountdownLatch 构造函数接收一个 int 类型的参数作为计数器,如果想等待 N 个点,就传入 N
- 调用 CountdownLatch 的 countDown() 方法时,计数器 N 就会减 1
- 在等待线程中调用 await() 方法,会进入等待状态,直到计数器 N 变为 0 后再执行;
CountdownLatch 方式实现:
public class C { public static void main(String[] args) { int N = 3; CountDownLatch countDownLatch = new CountDownLatch(N); new Thread(new Runnable() { @Override public void run() { System.out.println("D 等待执行中..."); try { countDownLatch.await(); System.out.println("D 开始执行"); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); for (char threadName='A'; threadName <= 'C'; threadName++) { final String tN = String.valueOf(threadName); new Thread(new Runnable() { @Override public void run() { System.out.println(tN + " 正在执行"); try { Thread.sleep(100); } catch (Exception e) { e.printStackTrace(); } System.out.println(tN + " 执行完毕"); countDownLatch.countDown(); } }).start(); } } }
执行结果如下:
D 等待执行中... B 正在执行 A 正在执行 C 正在执行 C 执行完毕 A 执行完毕 B 执行完毕 D 开始执行
应用场景模拟:导出用户账单明细,按年月日三个维度进行计算并显示,年月日分别放入 3 个 Excel 中,最终将Excel 合并为一个 zip 压缩文件返回给用户。
public class Demo2 { public static void main(String[] args) { try { // 模拟跟路径,根据自己路径修改 String tempDir = "/C/workspace/File/excel"; // 模拟存放文件路径 String baseFileName = "202009171016"; String zipFileName = tempDir + "/" + baseFileName + ".zip"; int N = 3; CountDownLatch mDoneSignal = new CountDownLatch(N); ExecutorService executor = Executors.newFixedThreadPool(N); // 用户盛放 excel 路径 List
执行结果如下:
线程C执行完毕:10:38:01.454 线程B执行完毕:10:38:01.455 线程A执行完毕:10:38:01.455 所有数据处理完毕... 开始将数据打包zip...