一、概述
-
CountDownLatch 是Java中提供的一种同步工具类,用于控制多个线程之间的执行顺序和协调。
-
CountDownLatch 通过一个计数器来实现,该计数器初始化为一个正整数,表示需要等待的线程数目。每个线程执行完一定的任务后,会调用
countDown()
方法将计数器减1。当计数器减到0时,表示所有线程已经完成任务,等待在await()
方法处的线程被唤醒,继续执行后续操作。 -
以下是 CountDownLatch 的一些常见用途:
- 控制执行顺序:控制多个线程执行的顺序,让某个线程等待其他线程完成特定任务后再执行。例如,主线程可以在开始执行某项任务之前等待所有的工作线程就绪,然后在所有工作线程完成任务后继续执行。
- 线程协调:确保某个任务在其他线程完成后再执行,以避免并发问题。例如,主线程可以等待多个子线程完成任务后再继续执行,或者多个子线程可以等待主线程发出信号后才开始执行。
- 等待多个事件完成:例如等待多个子任务全部完成后再进行汇总操作。
-
CountDownLatch 的主要方法包括:
- await():使当前线程等待,一直阻塞直到计数器为零。
- countDown():递减计数器的值,表示一个操作已完成。
- getCount():获取当前计数器的值。
二、使用方法
-
一般用法:主线程等待多个子线程任务完成(请不要专牛角尖!这里的线程是自己创建的,可以使用 join 方法来完成,实际应用就不一定)
int threadCount = 3; CountDownLatch latch = new CountDownLatch(threadCount); for (int i = 0; i < threadCount; i++) { // 启动一些子线程来执行任务 Thread thread = new Thread(() -> { // 子线程执行任务 System.out.println("执行业务逻辑"); // 子线程任务完成,计数器减1 latch.countDown(); }); thread.start(); } // 主线程 等待所有线程完成任务 latch.await(); // 所有子线程完成任务后 主线程继续执行后续操作 System.out.println("执行后续业务");
-
在调用异步方法时,等待异步返回结果(这里就没有办法使用 join 了)
CountDownLatch latch = new CountDownLatch(1); // 假设 doSome 是一个异步方法 xxxxObj.doSome("一些参数", new AsyncInterface(state){ switch(state){ case SUCCESS: // 异步方法返回成功,通知主线程可以执行(当然异常你也可以自己处理) latch.countDown(); break; } }); // 主线程 等待异步方法完成 latch.await(); // 继续执行业务
三、测试示例
-
下面示例使用3个线程并发地将数字1到300写入三个临时文件,然后将这些文件合并为一个文件。主线程调用 latch.await() 方法等待所有线程写入完成。然后,我们使用 combineFiles() 方法将写入的部分文件合并成一个完整的文件。合并完成后,输出合并完成的信息。
注意,示例中的写入和合并代码是简化的,为了演示 CountDownLatch 的使用,并没有处理异常、缓冲区管理和错误处理等方面。在实际的应用中,你可能需要根据具体需求对其完善。
package top.yiqifu.study.p004_thread; import java.io.*; import java.util.concurrent.CountDownLatch; public class Test066_CountDownLatch { private static final int THREAD_COUNT = 3; private static final int NUMBERS_PER_THREAD = 100; private static final String OUTPUT_FILE = "numbers.txt"; public static void main(String[] args) throws InterruptedException, IOException { CountDownLatch latch = new CountDownLatch(THREAD_COUNT); for (int i = 0; i < THREAD_COUNT; i++) { int threadIndex = i; Thread thread = new Thread(() -> { try { writeNumbersToFile(threadIndex); } catch (IOException e) { e.printStackTrace(); } finally { latch.countDown(); } }); thread.start(); } latch.await(); // 等待所有线程写入完成 combineFiles(); // 合并文件 System.out.println("所有任务完成"); } private static void writeNumbersToFile(int threadIndex) throws IOException { // 先写入临时文件 String fileName = "numbers_" + threadIndex + ".txt"; FileWriter fileWriter = new FileWriter(fileName); int startNumber = threadIndex * NUMBERS_PER_THREAD + 1; int endNumber = startNumber + NUMBERS_PER_THREAD - 1; for (int i = startNumber; i <= endNumber; i++) { fileWriter.write(i + "\n"); } fileWriter.close(); } private static void combineFiles() throws IOException { FileWriter output = new FileWriter(OUTPUT_FILE); for (int i = 0; i < THREAD_COUNT; i++) { String fileName = "numbers_" + i + ".txt"; FileReader input = new FileReader(fileName); BufferedReader reader = new BufferedReader(input); String line; while ((line = reader.readLine()) != null) { output.write(line + "\n"); } reader.close(); // 删除临时文件 File file = new File(fileName); file.delete(); } output.close(); } }
与此类似的用途有很多,如分片下载文件,然后合并一个文件。网络爬虫分开爬取内容,然后统一分析等。有关等待异步方法返回的使用请看 Java 开发 Zookeeper 分布式锁,这篇文章中有有好几处异步方法都用到了 CountDownLatch 。反正 CountDownLatch 是一个好东西,值得掌握。