CountDownLatch 源码解读

什么是CountDownLatch

CountDownLatch称之为闭锁,它可以使一个或一批线程在闭锁上等待,等到其他线程执行完相应操作后,闭锁打开,这些等待的线程才可以继续执行。确切的说,闭锁在内部维护了一个倒计数器。通过该计数器的值来决定闭锁的状态,从而决定是否允许等待的线程继续执行。

常用方法

public CountDownLatch(int count):构造方法,count表示计数器的值,不能小于0,否者会报异常。

public void await() throws InterruptedException:调用await()会让当前线程等待,直到计数器为0的时候,方法才会返回,此方法会响应线程中断操作。

public boolean await(long timeout, TimeUnit unit) throws InterruptedException:限时等待,在超时之前,计数器变为了0,方法返回true,否者直到超时,返回false,此方法会响应线程中断操作。

public void countDown():让计数器减1

CountDownLatch使用步骤:

  1. 创建CountDownLatch对象
  2. 调用其实例方法 await(),让当前线程等待
  3. 调用 countDown()方法,让计数器减1
  4. 当计数器变为0的时候, 唤醒阻塞在CountDownLatch上的线程(用阻塞这个词不太严格,后续有讲)

源码解析:

1.CountDownLatch 初始化过程,初始化内部计数器(aqs 的status变量)

//创建一个CountDownLatch对象,需要等待四个任务执行
CountDownLatch countDownLatch = new CountDownLatch(4);

 public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }
//Sync为CountDownLatch 的内部类,初始化aqs的status 变量为 count
//看过笔记上一篇ReentrantLock分析, 对这一幕应该比较数据
     private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;

        Sync(int count) {
            setState(count);
        }

        int getCount() {
            return getState();
        }
           //判断计数器(staus)是否为0
        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }
        //对内部计数器减1,如果减完1等于0 返回true
        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;
            }
        }
    }

2. countDownLatch.await();

//调用的父类 aqs中的 acquireSharedInterruptibly方法
public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

 public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        //判断中断状态
        if (Thread.interrupted())
            throw new InterruptedException();
        //tryAcquireShared该方法为 内部类sync中的方法,具体实现见上文中sync类
	    //判断是否所有线程都已经执行完(status==0意味着 当前任务都已经执行完成)
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }
//是不是很熟悉?
//如果该当前线程为首节点就自旋等待所有任务执行完成,否则就挂起当前线程
//如果自旋的过程中发现任务已经执行完成,就唤醒所有等待在等待队列上的线程(所有调用await的线程)
  private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        //创建一个等待节点(具体实现请见笔者上篇reentrantlock解析)
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                //如果当前结点是等待队列中的第一个结点,判断当前任务是否执行完(status == 0),如果没有在执行的任务,重置head节点,并唤醒所有等待在 等待队列的线程(所有调用await的线程)
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                //shouldParkAfterFailedAcquire检测前置节点是否为等待的节点,是就直接返回true,执行parkAndCheckInterrupt挂起当前线程,
                //如果当前结点为首节点,就把当前结点的等待状态(waitstatus)设为-1,并返回false,继续自旋等待
                //具体详情,见笔者的reentrantLock 解析
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
			//如果该方法因为某些特殊情况意外的退出(没有获取锁就退出了),那么就取消当前线程的等待
            if (failed)
                cancelAcquire(node);
        }
    }

3.countDownLatch.countDown() 实现

 public void countDown() {
        sync.releaseShared(1);
    }
//syn父类aqs的方法
 public final boolean releaseShared(int arg) {
        //对内部计数器减1,如果等于0返回true 
        if (tryReleaseShared(arg)) {
            //唤醒所有等待在CountDownLatch上的阻塞线程
            doReleaseShared();
            return true;
        }
        return false;
    }
//唤醒所有等待线程
 private void doReleaseShared() {
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                //如果当前等待节点=-1,说明该线程被阻塞,唤醒它
                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)                   
                break;
        }
    }

该篇主要讲解了CountDownLatch 实现,对CountDownLatch使用场景有疑问的可以参考https://mp.weixin.qq.com/s/WUwuZjgECBgaWujKV6u4CQ该篇博文

文章部分源码笔者没有讲的很详细,没有讲到的地方在笔者的ReentrantLock 源码解读中可以找到对应的解释。

对文中有疑问的地方或者写的不对的地方欢迎交流

原创文章 15 获赞 22 访问量 6935

猜你喜欢

转载自blog.csdn.net/qq_31387317/article/details/103045850