AQS之CountDownLatch使用场景和源码解析

jdk版本:1.8

CountDownLatch是一个可以等待多个线程的并发包工具,远比thread.join好用的多,底层使用了AQS的技术,AQS使用了CAS乐观锁的技术,是一个多线程环境中常见工具。也就是说包含了并发包,多线程,AQS,CAS这些技术,全部都是面试中的常客,如果能搞懂CountDownLatch,对于面试和找工作还是会有一丢丢的帮助的。

//CountDownLatch.Class

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;
            }
        }
  }

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

上述代码分别是CountDownLatch的内部类Sysc和构造方法,构造方法入参为int类型,表示要等待的线程数,这个值会保存在Sysc的state中,从上述代码中可以看到tryAcquireShared方法目的是判断state是否为0。

其实CountDownLatch类中一共就没有几个方法,最主要的几个分别是构造方法,countDown方法,一个无参数await方法和一个带超时时间的await方法。

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


//AbstractQueuedSynchronizer.Class
public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
}
//CountDownLatch.Class
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;
            }
}
//AbstractQueuedSynchronizer.Class
protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

countDown方法从源码中我们能读出他的目的是降state中的值做减1操作,如果因为这个操作state等于0则会调用doReleaseShared()方法,释放共享锁。这里有一个compareAndSetState方法,这个方法继续向下看可以看出是unsafe.compareAndSwapInt这个方法,就是我们常说的cas,这个方法中的四个参数this代表这个被操作的对象,stateOffset代表state这个属性的偏移量,调用这个方法的时候jvm可以根据这个偏移量知道这个属性当前的值,expect这个参数代表预期值,update这个参数代表更新后的值,这个方法意思就是如果满足state==expect,则会将state的值更新为update,如果state!=expect则返回false,所以tryReleaseShared方法是一个死循环,会一直getstate这个值尝试进行更新操作,如果更新之前值被其他线程变更则再重新获取新的state值反复操作。至于doReleaseShared这个方法一会在await后再讲。光从语义上理解则是如果state==0,我们就会释放掉CountDownLacth的锁,让程序继续进行。

//CountDownLatch.Class
public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
}
//AbstractQueuedSynchronizer.Class
public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
}
//AbstractQueuedSynchronizer.Class
private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
}

await方法我们能看出来一样调用了tryAcquireShared这个方法,这个方法小于0的条件是,没有进行-1操作之前state就已经为0,到这我们就能看出,如果state为0的情况下,调用await方法是不会有阻塞的,说明阻塞代码在doAcquireSharedInterruptibly这个方法中。这个方法中的具体细节不贴了,自己对照代码看看,能看出来在进入这个方法后还会做一次释放锁的尝试,如果尝试成功会调用setHeadAndPropagate这个方法,这个方法读进去之后会发现里边调用的还是上文没有讲的doReleaseShared得方法,并将head置为当前node,仅从语义中我们得知在await方法中一样可以通过判断state的值来释放锁。而如果state依然是大于0的值则head则会将节点的状态置为SIGNAL,并再下一次循环中调用LockSupport.park(this)这个方法将自己阻塞在这里,如果有线程调用了unpark,则会继续执行并再次循环。

//AbstractQueuedSynchronizer.Class
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;
        }
}

而doReleaseShared方法的目的正是将head节点的阻塞解除,使其正常执行,LockSupport.unpark(s.thread);,达到解锁的目的。

CountDownLatch的总体流程基本就是这样,无论是在调用countDown的时候将state改为0还是await的时候state已经为0都可以解除阻塞恢复运行,不会因为代码顺序导致死锁问题。在我们的项目中,会定义一个和线程数相同的CountDownLatch对象,每一个线程执行完会进行一次countDown,主线程会在线程池执行后进行一次有超时时间的countDownLacth.await操作,用于统计整个线程池中的失败成功数量和运行时间。至于有超时时间的await方法是怎么实现的,自己看看吧,拜拜。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值