CountDownLatch分析

271 阅读3分钟

CountDownLatch分析

1. 属性分析

很简单,就一个Sync属性,CountDownLatch是基于AQS来实现的。一般来说,基于AQS实现的锁或者一些别的东西,里面都有个继承与AQS的静态内部类,功能的实现都是委托给这个静态内部类来实现的。这里也是。

private final Sync sync;

2. 构造方法

count合法性检查,创建同步器(Sync)

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

3. 主要方法分析

await

和这个类似的带等待超时的await。这里只列举一个,说明情况。

调用这个方法会使的当前线程一直等待,一直等待count变为0,或者这个线程被中断掉。如果当前的count等于0,方法会立即返回(也就是当前线程不会等待)。如果count大于0,当前线程就会等待。(其实就是被park住了),如果count变为0,或者当前线程被中断。这个方法才会返回。

public void await() throws InterruptedException {
    // await就是获取一个锁,注意,这里锁的模式是共享锁,在AQS里面,如果没有获取到锁,就会被park住。如果count变为0,
    sync.acquireSharedInterruptibly(1);
}

getCount

返回当前的count值。

public long getCount() {
    return sync.getCount();
}

countDown

countDown就是释放锁

public void countDown() {
    sync.releaseShared(1);
}

到这里,看到所有的功能是委托给Sync来实现的。下面就看看Sync具体是怎么做的

4. Sync分析

看代码,真没有多少。下面就一点点来分析是怎么做的。

也就是说,每次countDown。就是归还锁,但是Sync重写了tryReleaseShared,理论上来说应该先获取锁,然后再释放锁,但是这里不是,先释放锁,tryReleaseShared如果返回为true表示,释放锁成功,应该唤醒等待的节点。所以,在tryReleaseShared里面,只有当state为0的时候才会返回true,其余的都是--。

对于await方法来说,调用的是获取锁的操作,因为在AQS里面获取锁失败会使的当前线程park住。同样的tryAcquireShared方法返回值大于等于0 表示当前线程获取到锁,反之表示当前线程没有获取到锁,需要park。在Sync里面,只有在State变为0的时候,才会返回1。表示获取到锁。当前线程就返回。如果没有获取到锁,就阻塞。只能等待countDown了。

  private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;
       // 在构造方法里面调用AbstractQueuedSynchronizer里面的setState方法,State表示count。
        Sync(int count) {
            setState(count);
        }
			  // 直接返回State
        int getCount() {
            return getState();
        }
        // 如果state不是0,就返回1.表示获取到锁。
        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

        protected boolean tryReleaseShared(int releases) {
            for (;;) {
               // 拿到state
                int c = getState();
               // 如果c等于0.说明不能堵塞了。直接返回
                if (c == 0)
                    return false;
                int nextc = c-1;
               // 重新设置state
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }

这里会涉及到一个概念 AQS的独占模式和共享模式

独占模式

同一时刻,只有一个线程可以获取到锁(互斥)

共享模式

同步时刻,多个线程可以获取到锁,这个多个线程获取锁的是不一定的。(共享)

举个列子说明两个的一个小差别

    @Test
    public void testCCountDownLatch() throws InterruptedException {
       // CountDownLatch值为2.
        CountDownLatch countDownLatch = new CountDownLatch(2);
        for (int i = 0; i < 2; i++) {
            new Thread(() -> {
                try {
                    TimeUnit.SECONDS.sleep((int) Math.random() * 10);
                    logMessage("start");
                    countDownLatch.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
       // 当state变为0之后,这四个线程会连续唤醒,如果是独占模式,就不可能发生这种情况,只能唤醒一个。
        for (int i = 0; i < 4; i++) {
            countDownLatch.await();
            logMessage("begin" + i);
        }
    }
public static void logMessage(Object o) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println(simpleDateFormat.format(new Date()) + "-" + Thread.currentThread().getName() + ":" + o);
    }

image-20211019231908842.png

额外补充

看上面内容的时候突然在org.apache.rocketmq.common也有一个CountDownLatch2。很奇妙,来看看他有什么特别的地方。


public class CountDownLatch2 {
    private final CountDownLatch2.Sync sync;

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

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

    public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
        return this.sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }

    public void countDown() {
        this.sync.releaseShared(1);
    }

    public long getCount() {
        return (long)this.sync.getCount();
    }

    public void reset() {
        this.sync.reset();
    }

    public String toString() {
        return super.toString() + "[Count = " + this.sync.getCount() + "]";
    }

    private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;
        private final int startCount;

        Sync(int count) {
            this.startCount = count;
            this.setState(count);
        }

        int getCount() {
            return this.getState();
        }

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

        protected boolean tryReleaseShared(int releases) {
            int c;
            int nextc;
            do {
                c = this.getState();
                if (c == 0) {
                    return false;
                }

                nextc = c - 1;
            } while(!this.compareAndSetState(c, nextc));

            return nextc == 0;
        }

        protected void reset() {
            this.setState(this.startCount);
        }
    }
}

百分之90都是一样的,这里增加了一个reset方法。用于将state变为一开始的值。也就是说,在countDown一段之后,可以重新将state设置为初始状态。重新设置值在CountDownLatch里面是不支持的。这个值只有在new的时候才能设置,别的地方没有这个操作。

关于CountDownLatch的分析就分析到这里了。 如有不正确的地方,欢迎指出。谢谢。