10 CountDownLatch


  Latch在英文中是门闩的意思,这个词用得非常贴切。CountDownLatch是一个倒计时。先让所有线程await。然后有线程调用countDown()方法之后,才能运行。当然CountDownLatch可以设置一个值N,只有调用N次后,await的线程才可以执行。
  举个例子,所有线程完成之后,才可以执行下一步。等于说有两个用途:
  一、N=1时,只有开关开启时,其他所有线程才能继续运行。
  二、N>1时,只有倒计时结束时,等待的线程才能执行。

开关用法

  这是的N=1的使用场景,以下是例子:

public static void main(String[] args) {
    final CountDownLatch latch = new CountDownLatch(1);
    for (int i = 0; i < 5; i++) {
        new Thread(() -> {
            try {
                latch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"执行中");
        }).start();
    }

    System.out.println("现在开始");
    latch.countDown();
}

  执行结果:
在这里插入图片描述

倒计时用法

  这是N>1的场景,以下是代码实例:

final CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
    new Thread() {
        @Override
        public void run() {
            System.out.println(getName()+"运行中");
            countDownLatch.countDown();
        }
    }.start();
}
try {
    countDownLatch.await();
} catch (InterruptedException e) {
    e.printStackTrace();
}
System.out.println("所有线程执行完毕");

  结果:
在这里插入图片描述

缺陷

  CountDownLatch只能使用一次,这是它的缺陷。把上面的例子改写一下,让CountDownLatch重新await,但是会发现根本没效果。以下是我的测试代码:

package com.trend;

import java.util.concurrent.CountDownLatch;

public class Main {
    public static void main(String[] args) {
        final CountDownLatch countDownLatch = new CountDownLatch(4);
        startThreads(countDownLatch);
        waitAll(countDownLatch);
        System.out.println("所有线程执行完毕");
        startThreads(countDownLatch);
        waitAll(countDownLatch);
        System.out.println("所有线程执行完毕");
    }

    private static void waitAll(CountDownLatch countDownLatch) {
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private static void startThreads(CountDownLatch countDownLatch) {
        for (int i = 0; i < 4; i++) {
            new Thread() {
                @Override
                public void run() {
                    System.out.println(getName()+"运行中");
                    countDownLatch.countDown();
                }
            }.start();
        }
    }
}

  打印结果如下:

Thread-1运行中
Thread-2运行中
Thread-0运行中
Thread-3运行中
所有线程执行完毕
Thread-4运行中
所有线程执行完毕
Thread-5运行中
Thread-6运行中
Thread-7运行中

  可见await没有任何效果。

原理

  现在面试可恶心了哈,原理什么的,总是问一大堆,所幸CountDownLatch的原理比较简单了。
  首先第一个问题,为什么会wait,wait在什么地方?源码是这样的:

public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

  展开代码是这样:

public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        return tryAcquireShared(arg) >= 0 ||
            doAcquireSharedNanos(arg, nanosTimeout);
    }

  等待的原因在于doAcquireSharedNanos中:

private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (nanosTimeout <= 0L)
            return false;
        final long deadline = System.nanoTime() + nanosTimeout;
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return true;
                    }
                }
                nanosTimeout = deadline - System.nanoTime();
                if (nanosTimeout <= 0L)
                    return false;
                if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

  这个for (;;)无限循环造成了线程的等待,这种策略也称为自旋。它一直在自旋等待,所以是没有切换线程状态的,我们把上述倒计时用法的代码,在countDown处打个断点就知道了:
在这里插入图片描述
  这时候断住了,但是再看看主线程的状态:
在这里插入图片描述
  主线程的状态是RUNNING,而不是WAITING,这就是自旋的证明。自旋的好处是免去了线程切换状态的开销,但是缺点是如果自旋次数太多,就很浪费CPU资源。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

醒过来摸鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值