多线程:如何确保多线程中所有任务执行完成

郎家岭伯爵 2023年02月15日 442次浏览

背景

在 多线程 的应用场景中,可能有些功能代码需要在所有任务执行完成后才可以继续执行,那么如何确保线程中所有任务都已执行完成了呢?

如以下代码,Hello,World!在任务完成之前就输出了:

import java.util.concurrent.*;

/**
 * @author 郎家岭伯爵
 */
public class Test {

    public static void main(String[] args) {
        // 开启线程池
        ExecutorService executorService = Executors.newFixedThreadPool(8);

        for (int i=0; i<8; i++){
            int finalI = i;
            executorService.execute(() -> {
                System.out.println("i:" + finalI);
            });
        }

        // 关闭线程池
        executorService.shutdown();

        System.out.println("Hello,World!");
    }
}

但如果业务场景要求在所有任务执行完之后才可以输出 Hello,World! ,那应该如何实现呢?

实现

理论

CountDownLatch 这个类能够使一个线程等待其他线程完成各自的工作后再执行。例如,应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行。

CountDownLatch 是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减 1。当计数器值到达 0 时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。

构造器中的计数值(count)实际上就是闭锁需要等待的线程数量。这个值只能被设置一次,而且CountDownLatch没有提供任何机制去重新设置这个计数值。

与 CountDownLatch 的第一次交互是主线程等待其他线程。主线程必须在启动其他线程后立即调用 CountDownLatch.await() 方法。这样主线程的操作就会在这个方法上阻塞,直到其他线程完成各自的任务。

其他 N 个线程必须引用闭锁对象,因为他们需要通知 CountDownLatch 对象,他们已经完成了各自的任务。这种通知机制是通过 CountDownLatch.countDown() 方法来完成的;每调用一次这个方法,在构造函数中初始化的 count 值就减 1。所以当 N 个线程都调用了这个方法, count 的值等于 0,然后主线程就能通过 await() 方法,恢复执行自己的任务。

实践

import java.util.concurrent.*;

/**
 * @author 郎家岭伯爵
 */
public class Test {

    public static void main(String[] args) throws InterruptedException {
        // 开启线程池
        ExecutorService executorService = Executors.newFixedThreadPool(8);

        // 参数与多线程的任务数必须保持一致,否则 count 会提前减到0,导致多线程之外的任务提前执行
        CountDownLatch countDownLatch = new CountDownLatch(16);

        for (int i=0; i<16; i++){
            int finalI = i;
            executorService.execute(() -> {
                System.out.println("i:" + finalI);
                countDownLatch.countDown();
            });
        }

        // 关闭线程池
        executorService.shutdown();

        countDownLatch.await();

        System.out.println("Hello,World!");
    }
}

总结

CountDownLatch 可以通过对任务计数的方式保证多线程中的所有任务都完成再继续执行其它代码。