CountDownLatch应用与分析

242 阅读2分钟

应用方式

实际CountDownLatch()的作用就是协调多个线程之间等待,所有线程都结束才能继续往下走。

现实中举个例子:

三个人一起去吃饭,总有吃的快的和吃的慢的,先吃完的接着走是不是不太好看?所以需要等其他人。怎么才能做到让先吃完饭的人等还没吃完的?其实就是用的countDown()和await()方法打配合。

主要api

方法名方法作用
CountDownLatch(int count)初始化,并且定义好技术器的数量
await()阻塞线程,一直到计数器为0为止
countDown()计数器减1
getCount()获取当前的计数
toString()重写过的,super.toString() + "[Count = " + sync.getCount() + "]"

代码示例

public static void main(String[] args) throws InterruptedException {
    // 定义好要被调用的总数
    CountDownLatch countDownLatch = new CountDownLatch(90);

    new Thread(()->{
        for (int i = 0; i<30;i++){
            try {
                Thread.sleep(1000);
                System.out.println("我是一号选手" +  countDownLatch.getCount());
                countDownLatch.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }).start();

    new Thread(()->{
        for (int i = 0; i<30;i++){
            try {
                Thread.sleep(1000);
                System.out.println("我是老二选手" +  countDownLatch.getCount());
                countDownLatch.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }).start();

    new Thread(()->{
        for (int i = 0; i<30;i++){
            try {
                Thread.sleep(1000);
                System.out.println("我是三号选手~" +  countDownLatch.getCount());
                countDownLatch.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }).start();

    System.out.println("你们三个得一起结束");
    countDownLatch.await();

    System.out.println("终于都结束了,over over");
}

执行结果

结果很长,就截最后那一点了

我是老二选手18
我是一号选手17
我是一号选手15
我是三号选手~15
我是老二选手15
我是一号选手12
我是三号选手~12
我是老二选手12
我是一号选手9
我是三号选手~9
我是老二选手9
我是老二选手6
我是三号选手~6
我是一号选手6
我是老二选手3
我是一号选手3
我是三号选手~3
终于都结束了,over over

Process finished with exit code 0

根预想的结果不太一样,确实一共也是90条,我以为能依次减下来,结果这跳跃式的?只能再去看源码找原因

阅读源码

CountDownLatch(int count)

可以发现他调用了一个Sync()

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

实际这是他定义的一个内部类,看来多线程的底层绕不开AQS

private static final class Sync extends AbstractQueuedSynchronizer {

    private volatile int state;

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

}

发现这个计数器,是用volatile修饰的

volatile特性

  1. 可见性
  2. 有序性 所以他是没有原子性的,所以在获取的时候值未变很正常

countDown()

一样是调用了sync中的方法

public void countDown() {
    sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

核心是tryReleaseShared(arg),这里面是整了把乐观锁

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;
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}

compareAndSetState再往下跟代码就是native的了,其实这就是乐观锁+volatile保证数据的可靠性

结尾

原来这个计数器是乐观锁写的。。。。这次算是浅尝辄止,后面要好好的把AQS读一遍源码