多线程之CountDownLatch


CountDownLatch

1、概述

countdownlatch,对应的中文意思是倒数计时栅栏。对应着java中的线程,就是多个线程会同时来到栅栏,等待栅栏打开。

这个更适用于我们平常测试多线程条件下,模拟搞并发场景,多个请求同时发起请求来进行模拟业务逻辑是否还能够执行成功。

底层基于 AbstractQueuedSynchronizer 实现,CountDownLatch 构造函数中指定的count直接赋给AQS的state;每次countDown()则都是release(1)减1,最后减到0时unpark阻塞线程;这一步是由最后一个执行countdown方法的线程执行的。 而调用await()方法时,当前线程就会判断state属性是否为0,如果为0,则继续往下执行,如果不为0,则使当前线程进入等待状态,直到某个线程将state属性置为0,其就会唤醒在await()方法中等待的线程。

2、常用方法介绍

方法名称 描述
await() 线程挂起,直到count=o才会继续执行
boolean await(long timeout, TimeUnit unit) 等待time时间后,count的值还不是0,不再等待,那么将继续执行
countDown() 会将count的值-1,直到为0

3、使用两种场景

3.1、让多个线程等待

/**
 * @Description 多个线程等待,模拟并发  突发流量爆发!还是比较简单的
 * @Author liguang
 * @Date 2022/03/19/16:23
 */
public class CountDownLatchOne {
    public static void main(String[] args) {
        CountDownLatch countDownLatch = new CountDownLatch(1);
        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                try {
                    countDownLatch.await();
                    System.out.println("--------->>>>>"+Thread.currentThread().getName()+"开始执行");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
        System.out.println("=================模拟多个线程并发======================");
        try {
            Thread.sleep(2000);
            countDownLatch.countDown();  // 这个方法会让count的值变成0!从而让await的线程开始同事执行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

3.2、让单个线程等待

/**
 * @Description 多个线程等待,模拟并发  突发流量爆发!还是比较简单的
 * @Author liguang
 * @Date 2022/03/19/16:23
 */
public class CountDownLatchTwo {
    public static void main(String[] args) {
        CountDownLatch countDownLatch = new CountDownLatch(5);
        for (int i = 0; i < 5; i++) {
            int index = i;
            new Thread(()->{
                try {
                    Thread.sleep(1000+ ThreadLocalRandom.current().nextInt(100));
                    System.out.println(Thread.currentThread().getName()+"  finish task "+ index);
                    countDownLatch.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
        try {
            // 等待结果
            countDownLatch.await();
            System.out.println("所有任务完成之后,来计算任务...................");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

4、源码

首先从构造方法中可以看到:

public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count);
}


protected final void setState(int newState) {
    state = newState;
}

这里非常类似于Semaphore类

await方法

和Semaphore不同的地方在于:

protected int tryAcquireShared(int acquires) {
    return (getState() == 0) ? 1 : -1;
}

在获取得到许可的时候,这里只有两个选择。

1、如果许可不为0,那么返回-1;返回-1,表示的是需要进入到阻塞队列中来;

2、如果许可返回0,那么返回1;相当于是获取得到锁,直接执行业务逻辑;

核心

在使用await方法的时候,只有当许可为0的时候,才会去真正执行;不为0的时候,那么直接到阻塞线程中去

countDown方法

可以直接去看下源码:

        protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
                // 如果许可为0,那么直接返回false,否则继续减少许可;
                // 直到许可为0的时候,才会返回TRUE
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }

只有到位为true的时候,才会去真正的是释放锁:

    private void doReleaseShared() {
        /*
         * Ensure that a release propagates, even if there are other
         * in-progress acquires/releases.  This proceeds in the usual
         * way of trying to unparkSuccessor of head if it needs
         * signal. But if it does not, status is set to PROPAGATE to
         * ensure that upon release, propagation continues.
         * Additionally, we must loop in case a new node is added
         * while we are doing this. Also, unlike other uses of
         * unparkSuccessor, we need to know if CAS to reset status
         * fails, if so rechecking.
         */
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

这里和Semaphore是一样的代码,非常的类似简单。也是需要来将后面的进行唤醒。

核心

这里的代码在讲述的是线程进来的时候会首先尝试获取得到一次许可,当许可为0的时候,才会去唤醒阻塞队列中的阻塞的线程。

5、注意

和Semaphore一样,这里需要注意一下:

/**
 * @Description 多个线程等待,模拟并发  突发流量爆发!还是比较简单的
 * @Author liguang
 * @Date 2022/03/19/16:23
 */
public class CountDownLatchOne {
    public static void main(String[] args) {
        CountDownLatch countDownLatch = new CountDownLatch(2);
        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                try {
                    countDownLatch.await();
                    System.out.println("--------->>>>>"+Thread.currentThread().getName()+"开始执行");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
        System.out.println("=================模拟多个线程并发======================");
        try {
            Thread.sleep(2000);
            countDownLatch.countDown();  // 这个方法会让count的值变成0!从而让await的线程开始同事执行
            countDownLatch.countDown();  // 这个方法会让count的值变成0!从而让await的线程开始同事执行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

因为设置的许可为2,所以在countDown的时候,需要操作两次。

但是一般使用的方式也就是上面两个步骤,比较简单

6、CountDownLatch和Thread.join()方法的区别

  • 1、CountDownLatch的作用就是允许一个或多个线程等待其他线程完成操作,看起来有点类似join() 方法,但其提供了比 join() 更加灵活的API。

  • 2、CountDownLatch可以手动控制在n个线程里调用n次countDown()方法使计数器进行减一操作,也可以在一个线程里调用n次执行减一操作。 而 join() 的实现原理是不停检查join线程是否存活,如果 join 线程存活则让当前线程永远等待。所以两者之间相对来说还是CountDownLatch使用起来较为灵活。