CountDownLatch允许一个或多个线程等待其他线程完成操作。定义CountDownLatch的时候,需要传入一个正数来初始化计数器(虽然传入0也可以,但这样的话CountDownLatch没什么实际意义)。其countDown方法用于递减计数器,await方法会使当前线程阻塞,直到计数器递减为0。所以CountDownLatch常用于多个线程之间的协调工作。

CountDownLatch示例

假设我们现在有这样一个需求:

1.从数据库获取数据

2.对这批数据进行处理

3.保存这批数据

为了让程序执行效率更高,第2步中我们可以使用多线程来并行处理这批数据,大致过程如下所示:

public class CountDownLatchTest {

    public static void main(String[] args) throws InterruptedException {
        // 1. 模拟从数据库获取数据
        int[] data = query();
        System.out.println("获取数据完毕");

        // 2. 数据处理
        IntStream.range(0, data.length).forEach(i -> {
            ExecutorService.execute(() -> {
                System.out.println(Thread.currentThread() + "处理第" + (i + 1) + "条数据");
                int value = data[i];
                if (value % 2 == 0) {
                    data[i] = value * 2;
                } else {
                    data[i] = value * 10;
                }
            });
        });

        System.out.println("所有数据都处理完了");
        // 关闭线程池
        ExecutorService.shutdown();
        // 3. 保存数据
        save(data);
    }
    private static int[] query() {
        return new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    }
    private static void save(int[] data) {
        System.out.println("保存数据 - " + Arrays.toString(data));
    }
}

由于线程获取CPU时间片的不确定性,所以有可能数据还没有处理完毕,第3步就执行完了:

获取数据完毕
所有数据都处理完了
保存数据 - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Thread[pool-1-thread-2,5,main]处理第2条数据
Thread[pool-1-thread-1,5,main]处理第1条数据
Thread[pool-1-thread-2,5,main]处理第3条数据
Thread[pool-1-thread-1,5,main]处理第4条数据
Thread[pool-1-thread-1,5,main]处理第6条数据
Thread[pool-1-thread-2,5,main]处理第5条数据
Thread[pool-1-thread-1,5,main]处理第7条数据
Thread[pool-1-thread-1,5,main]处理第9条数据
Thread[pool-1-thread-2,5,main]处理第8条数据
Thread[pool-1-thread-1,5,main]处理第10条数据

我们可以借助CountDownLatch解决这个问题:

public class CountDownLatchTest {
    private static ExecutorService ExecutorService = Executors.newFixedThreadPool(2);
    private static CountDownLatch latch = new CountDownLatch(10);

    public static void main(String[] args) throws InterruptedException {
        // 1. 模拟从数据库获取数据
        int[] data = query();
        System.out.println("获取数据完毕");

        // 2. 数据处理
        IntStream.range(0, data.length).forEach(i -> {
            ExecutorService.execute(() -> {
                System.out.println(Thread.currentThread() + "处理第" + (i + 1) + "条数据");
                int value = data[i];
                if (value % 2 == 0) {
                    data[i] = value * 2;
                } else {
                    data[i] = value * 10;
                }
                latch.countDown();
            });
        });

        latch.await();
        System.out.println("所有数据都处理完了");
        // 关闭线程池
        ExecutorService.shutdown();
        // 3. 保存数据
        save(data);
    }
    private static int[] query() {
        return new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    }
    private static void save(int[] data) {
        System.out.println("保存数据 - " + Arrays.toString(data));
    }
}

我们定义了一个CountDownLatch,计数器值为10,和数据量一致。然后在第2步中,当每个线程执行完毕的时候调用countDown方法,让计数器减1。在第3步前调用await方法让main线程阻塞等待,直到计数器被减为0。所以这就保证了只有当所有数据加工完毕才执行保存数据操作。

执行方法,程序输出如下所示:

获取数据完毕
Thread[pool-1-thread-1,5,main]处理第1条数据
Thread[pool-1-thread-1,5,main]处理第3条数据
Thread[pool-1-thread-1,5,main]处理第4条数据
Thread[pool-1-thread-1,5,main]处理第5条数据
Thread[pool-1-thread-1,5,main]处理第6条数据
Thread[pool-1-thread-1,5,main]处理第7条数据
Thread[pool-1-thread-1,5,main]处理第8条数据
Thread[pool-1-thread-1,5,main]处理第9条数据
Thread[pool-1-thread-1,5,main]处理第10条数据
Thread[pool-1-thread-2,5,main]处理第2条数据
所有数据都处理完了
保存数据 - [10, 4, 30, 8, 50, 12, 70, 16, 90, 20]

await有重载方法:await(long timeout, TimeUnit unit),设置最大等待时间,超过这个时间程序将继续执行不再被阻塞:

public class CountDownLatchTest {
    public static void main(String[] args) throws InterruptedException {
        final CountDownLatch latch = new CountDownLatch(1);
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(4);
                System.out.println(Thread.currentThread() + "线程执行完毕");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                latch.countDown();
            }
        }, "thread1").start();

        latch.await(3, TimeUnit.SECONDS); // 最多等待 3秒
        System.out.println("main线程执行完毕");
    }
}

输出如下:

main线程执行完毕
Thread[thread1,5,main]线程执行完毕

特别声明:

本文转载自Mrbird博客:https://mrbird.cc/JUC-CountDownLatch.html

   
     
本文作者:      
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!
扫码关注不迷路!

1 对 “JUC之CountDownLatch介绍及使用”的想法;

波比大佬的小迷弟进行回复 取消回复

电子邮件地址不会被公开。 必填项已用*标注