CountDownLatch实现原理

来源:互联网 发布:2017年10月非农数据 编辑:程序博客网 时间:2024/06/05 19:54

CountDownLatch用过很多次了,突然有点好奇,它是如何实现阻塞线程的,猜想是否跟LockSupport有关。今天浏览了一下它的源码,发现其实现是十分简单的,只是简单的继承了AbstractQueuedSynchronizer,便实现了其功能。


实现原理:让需要的暂时阻塞的线程,进入一个死循环里面,得到某个条件后再退出循环,以此实现阻塞当前线程的效果。


下面从构造方法开始,一步步解释实现的原理:

一、构造方法

下面是实现的源码,非常简短,主要是创建了一个Sync对象。

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

 private static final class Sync extends AbstractQueuedSynchronizer {        private static final long serialVersionUID = 4982264981922014374L;        Sync(int count) {            setState(count);        }        int getCount() {            return getState();        }        protected int tryAcquireShared(int acquires) {            return (getState() == 0) ? 1 : -1;        }        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;            }        }    }

假设我们是这样创建的:new CountDownLatch(5)。其实也就相当于new Sync(5),相当于setState(5)。setState我们可以暂时理解为设置一个计数器,当前计数器初始值为5。


tryAcquireShared方法其实就是判断一下当前计数器的值,是否为0了,如果为0的话返回1(返回1的时候,就表明当前线程可以继续往下走了,不再停留在调用countDownLatch.await()这个方法的地方)。


tryReleaseShared方法就是利用CAS的方式,对计数器进行减一的操作,而我们实际上每次调用countDownLatch.countDown()方法的时候,最终都会调到这个方法,对计数器进行减一操作,一直减到0为止。


稍微跑偏了一点,我们看看调用countDownLatch.await()的时候,做了些什么。

三、countDownLatch.await()

 public void await() throws InterruptedException {        sync.acquireSharedInterruptibly(1);    }
代码很简单,就一句话(注意acquireSharedInterruptibly()方法是抽象类:AbstractQueuedSynchronizer的一个方法,我们上面提到的Sync继承了它),我们跟踪源码,继续往下看:

四、acquireSharedInterruptibly(int arg)

 public final void acquireSharedInterruptibly(int arg)            throws InterruptedException {        if (Thread.interrupted())            throw new InterruptedException();        if (tryAcquireShared(arg) < 0)            doAcquireSharedInterruptibly(arg);    }

源码也是非常简单的,首先判断了一下,当前线程是否有被中断,如果没有的话,就调用tryAcquireShared(int acquires)方法,判断一下当前线程是否还需要“阻塞”。其实这里调用的tryAcquireShared方法,就是我们上面提到的java.util.concurrent.CountDownLatch.Sync.tryAcquireShared(int)这个方法。

当然,在一开始我们没有调用过countDownLatch.countDown()方法时,这里tryAcquireShared方法肯定是会返回1的,因为会进入到doAcquireSharedInterruptibly方法。

五、doAcquireSharedInterruptibly(int arg)


这个时候,我们应该对于countDownLatch.await()方法是怎么“阻塞”当前线程的,已经非常明白了。其实说白了,就是当你调用了countDownLatch.await()方法后,你当前线程就会进入了一个死循环当中,在这个死循环里面,会不断的进行判断,通过调用tryAcquireShared方法,不断判断我们上面说的那个计数器,看看它的值是否为0了(为0的时候,其实就是我们调用了足够多次数的countDownLatch.countDown()方法的时候),如果是为0的话,tryAcquireShared就会返回1,代码也会进入到图中的红框部分,然后跳出了循环,也就不再“阻塞”当前线程了。需要注意的是,说是在不停的循环,其实也并非在不停的执行for循环里面的内容,因为在后面调用parkAndCheckInterrupt()方法时,在这个方法里面是会调用 LockSupport.park(this);,来禁用当前线程的。

五、关于AbstractQueuedSynchronizer

看到这里,如果各位对AbstractQueuedSynchronizer没有了解过的话,可能代码还是看得有点迷糊,这是一篇我觉得解释得非常好的文章,大家可以看一下:http://ifeve.com/introduce-abstractqueuedsynchronizer/