Java 并发编程系列之闭锁(CountDownLatch)

在讲闭锁之前,我们先来思考一个问题:在多线程环境下,主线程打印一句话,如何保证这句话最后(其他线程全部执行完毕)打印?

博主目前可以想到的实现方式有两种。一种是通过 join() 方法实现,另一种就是用闭锁。如果大家有好的解决办法,可以在下面留言,下面进入正题。

一、什么是闭锁

闭锁(CountDownLatch)是 java.util.concurrent 包下的一种同步工具类。闭锁可以用来确保某些活动直到其他活动都完成后才执行。

闭锁相当于一扇门:在闭锁到达结束状态之前,这扇门一直是关闭的,并且没有任何线程能通过,当达到结束状态时,这扇门会打开,并允许所有的线程通过。

下面是 CountDownLatch 中的方法:
Java 并发编程系列之闭锁(CountDownLatch)_第1张图片

CountDownLatch 中有一个计数器字段,在对象创建时初始化,表示需要等待的事件数量。countDown() 方法递减计数器,表示有一个事件已经发生了,await() 方法等待计数器为 0 时,表示所有的需要等待的时间都已经发生。如果计数器的值非 0 会一直阻塞直到计数器为 0。

下面使用闭锁来实现文章开头的问题:

public class CountDownLatchTest {
    /**
     * 初始化需要等待的 3 个事件
     */
    private static CountDownLatch latch = new CountDownLatch(3);

    public static void main(String[] args) throws InterruptedException {
        /**
         * 创建 3 个线程去执行事件
         */
        new Thread(() -> {
            System.out.println("*****_*****");
            latch.countDown();
        }).start();

        new Thread(() -> {
            System.out.println("*****_*****");
            latch.countDown();
        }).start();

        new Thread(() -> {
            System.out.println("*****_*****");
            latch.countDown();
        }).start();
        
        // 在计数器为 0 之前会一直阻塞
        latch.await();

        System.out.println("~~~~~_~~~~~");
    }
}

执行结果:
这里写图片描述

二、闭锁的用途

我们已经知道了闭锁的使用方法,但是闭锁都是有哪些应用场景呢?

  • 确保某个计算在其需要的所有资源都被初始化之后才执行
  • 确保某个服务在其依赖的所有其他服务都已经启动之后才启动
  • 等待直到每个操作的所有参与者都就绪再执行(比如打麻将时需要等待四个玩家就绪)

CountDownLatch 底层是基于 AQS(AbstractQueuedSynchronizer)实现的,关于 AQS 的知识会在后续的博文中进行分析,大家有兴趣的可以持续关注。

PS:在一开始提到了使用 join() 方法解决打印问题,下面把代码贴出来供大家参考。

public class ThreadJoinTest {

    public static void main(String[] args) throws InterruptedException {
        
        Thread thread1 = new Thread(() -> System.out.println("*****_*****"));
        Thread thread2 = new Thread(() -> System.out.println("*****_*****"));
        Thread thread3 = new Thread(() -> System.out.println("*****_*****"));
        
        thread1.start();
        thread2.start();
        thread3.start();
        
        /**
         * 作用:在 A 线程中调用了 B 线程的 join() 方法时,
         * 表示只有当 B 线程执行完毕时,A 线程才能继续执行
         * 原理:调用了当前线程的 wait() 方法
         */
        
        thread1.join();
        thread2.join();
        thread3.join();

        System.out.println("~~~~~_~~~~~");
    }
}

参考资料

《Java 并发编程实战》

你可能感兴趣的:(Java,基础)