JUC同步器 CountDownLatch原理学习

CountDownLatch

主线程等待固定数量的其他线程运行完成之后,继续执行后面的操作。

内部类 Sync

同步控制器 Sync 类中实现获取共享锁的 tryAcquireShared 方法和释放共享锁的 tryReleaseShared 方法。此处的 Sync 不是抽象类。

tryAcquireShared 方法中仅仅判断当前状态(状态用于判断是否打开 latch)是否等于 0,当状态值降为 0 时,打开 latch,线程可以获取到锁,否则阻塞在 latch 之外。

在 tryReleaseShared 方法中自旋尝试 count 计数减 1,若减 1 之后的状态计数等于 0,则在调用此函数的 acquireSharedInterruptibly 中释放所有线程。

        //调用此函数的方法是 acquireSharedInterruptibly
        // 在 acquireSharedInterruptibly 中,返回值小于 0 时进入 AQS 队列等待
        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }
        
        // 释放共享锁
        protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c-1;
                // 通过 CAS 设置同步状态
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }

成员函数

此类的核心函数是 await 和 countDown。

此类的一个常用场景是,主线程等待固定数量的子线程运行完成之后,继续运行后面的操作。使用方法 await 阻塞主线程,直到所有的线程都完成调用 countDown 方法,将状态计数减到 0 之后,被阻塞的主线程才开始继续运行。

构造函数指定 count 的值,代表需要调用 countDown 多少次才会打开 latch。由于在构造函数处指定,且无法调用任何函数更改,所以 CountDownLatch 只能用一次。

在 Sync 中实现了共享模式获取锁和释放锁的函数之后,CountDownLatch 类中的 await 和 countDown 函数只需要简单的调用需要的函数即可。

    /**
     * 使当前线程等待,直到 latch 的计数降到 0,等待过程中响应中断。
     *
     * 如果当前计数为 0,则此方法立即返回(获取成功)。
     *
     * 如果当前计数大于 0,则出于线程调度的目的,当前线程将被禁用,并休眠
     * 直到发生以下两种情况之一:
     * 由于调用 countDown 方法当前计数降到 0;或者其他线程中断此线程。
     *
     * 如果当前线程:
     * 在进入此方法前设置了中断状态;或者在等待时被中断,
     * 那么抛出 InterruptedException 异常,并清除当前线程的中断状态。
     *
     * @throws InterruptedException if the current thread is interrupted
     *         while waiting
     */
    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
    
    /**
     * latch 的计数递减,如果计数达到 0,则释放所有的等待线程。
     *
     * 如果当前计数大于 0,则递减。如果新的计数为 0,那么所有等待的线程都
     * 将重新启用,以便进行线程调度。
     *
     * 如果当前计数等于 0,则什么也不会发生。
     */
    public void countDown() {
        sync.releaseShared(1);
    }

应用实例

CountDownLatch 创建时指定 count 为 3,main 主线程在 latch.await 处等待三个子线程完成 latch.countDown 操作之后,才继续执行后面的操作。

public class test {
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(3);
        CountDownLatch latch = new CountDownLatch(3);
        for (int i = 0; i < 3; i++) {
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println("子线程 " + Thread.currentThread().getName() + " 开始执行");
                        Thread.sleep((long) (Math.random() * 10000));
                        System.out.println("子线程 " + Thread.currentThread().getName() + " 执行完毕");
                        latch.countDown();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            };
            service.execute(runnable);
        }
        try{
            System.out.println("主线程 " + Thread.currentThread().getName() + " 等待子线程完成");
            latch.await();
            System.out.println("主线程 " + Thread.currentThread().getName() + " 执行完毕");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

测试 CountDownLatch 是否可以一次唤醒多个等待的线程:

public class test {
    public static void main(String[] args) throws InterruptedException{
        final int count = 10;
        CountDownLatch latch = new CountDownLatch(count);

        ExecutorService executor = Executors.newFixedThreadPool(3);
        Runnable task = new Runnable() {
            @Override
            public void run() {
                try {
                    latch.await();
                } catch(Exception e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " run.");
            }
        };
        for (int i = 0; i < 2; i++) {
            executor.execute(task);
        }

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                for (int i = 0; i < count; i++) {
                    latch.countDown();
                }
            }
        }).start();

        executor.shutdown();
    }
}

结果如下:

pool-1-thread-1 run.

pool-1-thread-2 run.

总结

CountDownLatch 底层实现依赖于 AQS 共享锁的实现机制,首先初始化计数器 count,调用 countDown 方法时,计数器 count 减 1。当计数器 count 等于 0 时,会唤醒 AQS 等待队列中的线程。

调用 await 方法,线程会被挂起,它会等待直到 count 值为 0 才继续执行,否则会加入到等待队列中,等待被唤醒。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值