CountDownLatch与Semaphore快速上手与实现原理

133 阅读2分钟

要想明白CountDownLatch的实现原理,必须对AQS足够熟悉,可以参考:从ReentrantLock到AQS,到底和synchronized有啥区别

CountDownLatch:等待唤醒

中文翻译过来就是倒计时锁

作用:

用于某个线程在执行任务之前,需要等待其它线程完成一些前置任务,必须等所有的前置任务都完成,才能开始执行本线程的任务。

快速使用

就两个API :wait 和 countdown

state:还需等待几个线程

  • 对于调用countDown方法的线程,不关心state具体的值
  • 对于调用await方法的线程,state代表还需要等待多少个线程调用countDown方法

构造方法:指定state初始值

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

await:阻塞等待执行

await对应获取资源,因为自定义了tryAcquireShared方法,只有state为0才能获取资源

countDown:state--

countDown对应释放资源,自旋CAS让state = state - 1

当state被减为0,所有调用await阻塞的线程会按顺序一个个醒来,继续执行它们的任务

实现原理

因为也完全是依托AQS而实现的,所以只关注重写的两个模板方法

CountDownLatch是个共享锁,所以只需要重写两个方法

// State为0 获取锁成功
protected int tryAcquireShared(int acquires) {
    return (getState() == 0) ? 1 : -1;
}
// CAS自旋 让state--
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;
    }
}

应用场景

RocketMQ源码中大量出现了CountDownLatch(1)的使用,即当一个线程执行完毕,这个线程的执行结果被所有调用await的线程所依赖,我们希望所有调用await的线程都醒来,而wait/notifyAll是个独占锁就做不到这样的功能。当可能不止一个线程依赖某个线程的执行结果时,CountDownLatch(1)就能做到wait/notify做不到的。

Semaphore:控制线程数量

作用:控制「访问资源/执行某段程序」的线程数量

快速使用

主要是acquire获取许可证,release释放许可证

permits/state:最大线程数量

即所控制的最大线程数量

构造方法:指定state/公平

    public Semaphore(int permits) {
        sync = new NonfairSync(permits);
    }
    public Semaphore(int permits, boolean fair) {
        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
    }

acquire:获取许可证

无参acquire,获取一个,可以指定获取的许可证个数

release:释放许可证

类似acquire

实现原理

和CountDownLatch类似,我们只看非公平的两个模板方法吧

// CAS自旋 让state-acquires 
final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}
// CAS自旋 让state+releases
protected final boolean tryReleaseShared(int releases) {
    for (;;) {
        int current = getState();
        int next = current + releases;
        if (next < current) // overflow
            throw new Error("Maximum permit count exceeded");
        if (compareAndSetState(current, next))
            return true;
    }
}

小结

可以看到,通过AQS我们可以快速的自定义共享/独占锁,通过这两个例子不仅仅认识了两个好用的工具类,也是更进一步理解AQS的应用。