CountDownLatch 源码浅析

简介

CountDownLatch提供的是一个计数器倒数的功能。所有调用了await(long timeout, TimeUnit unit)/await()方法的线程都处于阻塞的状态,通过countDown()方法递减计数器,当计数器递减为0或线程中断的时候(await(long timeout, TimeUnit unit)在超时时间达到的情况下也会唤醒线程)唤醒AQS队列中的线程,线程重新恢复运行。

关键方法

public CountDownLatch(int count);//构造方法,创建CountDownLatch对象,指定计数器
public void await();//线程阻塞,等待计数器为0后结束阻塞
public boolean await(long timeout, TimeUnit unit);//线程在一定的时间内阻塞
public void countDown();//计数值减一
public long getCount();//获取计数器当前值

方法实现

1. CountDownLatch(int count)构造器实现

    /**
     * 根据给定的计数器值初始化CountDownLatch对象,计数器值不能小于0
     */
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }
   /**
    * Sync内部类,继承AbstractQueuedSynchronizer(著名的AQS)
    */
    private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;
        Sync(int count) {
        //设置计数器值,计数器为volatile修饰
            setState(count);
        }
		//获取计数器当前值
        int getCount() {
            return getState();
        }
		//尝试获取共享锁,即判断计数器是否归零,归零则获取锁成功
        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }
		//尝试释放共享锁,计数器值递减,判断计数器是否归零,归零则释放共享锁
        protected boolean tryReleaseShared(int releases) {
            // for循环+CAS操作,for循环保证操作成功,CAS保证原子性操作
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }

2. await()方法实现

await方法主要功能就是阻塞线程,直到计数器归零后,线程恢复运行。

实现逻辑:

  1. 尝试获取共享锁(即判断计数器值是否为0),获取共享锁成功则直接返回,线程不阻塞
  2. 在获取共享锁失败的情况下,将当前线程加入到AQS队列中
  3. 如果当前线程所在节点的前驱为AQS队列的头结点(head),再次尝试获取锁(因为AQS队列是FIFO,前驱为头结点,那么有可能此时计数器已经归零了,所以需要再次尝试),获取锁成功,当前线程所在的节点设置为头结点(head),并向后传播。
  4. 在尝试获取共享锁不成功的情况下,判断是否需要将线程挂起(首节点为SIGNAL的状态下需要挂起,会剔除队列中CANCLED状态的节点),在需要挂起的情况下,阻塞线程,将其挂起。等待计数器归零,调用LockSupport.unpark(thread);恢复线程的运行。

代码分析:

	/**
	 * 阻塞线程线程,直到计数器归零或线程中断
	 * 如果计数器的值已经为0,该方法立即返回
	 * 
	 * /
    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
	/**
	 * 已可中断的方式获取共享锁,首先判断线程的中断状态,然后至少调用一次
	 * tryAcquireShared(获取共享锁成功)。否则线程会被加入到AQS队列中,可能会发生重复的
	 * 阻塞和运行,调用tryAcquireShared直到获取共享锁成功或线程被中断。
	 * /
    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        //判断线程是否中断,中断抛异常
        if (Thread.interrupted())
            throw new InterruptedException();
        //尝试获取共享锁,获取成功,立即返回,获取失败,加入等待队列,挂起线程
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }
	/**
	 * 获取共享锁,await方法的关键部分代码
	 */
	private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        //将当前线程封装成Node加入AQS队列,节点状态为SHARED状态,返回当前线程的Node节点
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
       		 //循环处理,当中断的线程被唤醒后,会重新执行此循环,并进入if(p==head)的分支,
       		 //在setHeadAndPropagate()方法中会重新设置头结点,并且会唤醒当前节点。实现节
       		 //点唤醒的向后传递
            for (;;) {
            	//获取节点的前驱,前驱一定不为空,因为在入队操作时(enq方法),
            	//在队列为空的时候,会创建空节点作为头结点,然后在头结点后面增加当前节点
                final Node p = node.predecessor();
                //前驱为头节点,那么有可能计数器已经归零(释放),当前节点尝试去获取共享锁,
                //获取成功,则设置当前节点为头结点,并且
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                    	//尝试获取共享锁成功后,需要将当前节点设置为 AQS 队列中的第一个节点,
                    	//这是 AQS 的规则,队列的头节点表示正在获取锁的节点 ,并且传播释放下一个节点
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                //shouldParkAfterFailedAcquire方法中只有当前驱节点为SIGNAL状态下才返回true
                //如果前驱为CANCLED状态,则递归移除CANCLED状态的节点
                //如果前驱为其他状态(CONDITION/PROPAGATE)会将其设置为SIGNAL状态
                if (shouldParkAfterFailedAcquire(p, node) &&
                //调用LockSupport.park();挂起线程,当unpark被调用的时候,恢复运行,
                //for循环重新获取共享锁,设置为头结点并向后传播,直到所有的节点都获得共享锁,继续执行
                 //在线程恢复运行后判断线程是否中断并判断线程是否中断(中断并复位)
                 //线程恢复有两种方式:1. 主动唤醒线程unpark(thread);  2. 线程被中断
                    parkAndCheckInterrupt()) 
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
	/**
	 * 将线程加入到AQS队列中
	 */
    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        //队列中已有节点,则尝试在已有链表的尾节点后面加入当前节点,加入成功,返回
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //
        enq(node);
        return node;
    }
	/**
	 * 节点加入队列
	 */
    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
            	//如果队列原始状态为空,则创建一个空的节点作为头节点
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }
	/**
	 * 设置队列的头节点,并且AQS队列向后传播,释放队列中处于BLOCKED状态下线程
	 * 
	 * /
    private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        setHead(node);
   		
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
            	// unpark AQS队列中的后继节点
                doReleaseShared();
        }
    }

3. await(long timeout, TimeUnit unit)方法实现

await(long timeout, TimeUnit unit)方法主要功能是实现线程在一定的时间内处于BLOCKED状态,其中有三种方式可以实现线程继续运行:

  • 计数器归零
  • 达到指定的阻塞时间
  • 线程被中断

实现逻辑:

  1. 尝试获取共享锁(计数器归零),如果获取成功,则直接返回,不阻塞
  2. 与await()一样,将当前线程节点加入到AQS队列中,并判断前驱是否为head节点,如果是,则尝试获取共享锁,获取成功,重新设置头结点并向后传递
  3. 如果不是head节点或尝试获取共享锁不成功,通过1微秒为临界值判断是将线程挂起指定的时间还是通过自循环(主要是因为挂起线程涉及到操作系统层面用户态和内核态的切换,代价较高)。
  4. 线程恢复运行之后通过for循环可以实现线程重新运行。
	/**
	 * 当前线程等待直到计数器归零或线程中断或超过等待时间
	 * 如果计数器归零,则返回true
	 * 如果线程被其他线程中断,则抛出InterruptedException异常
	 * 如果超过等待时间,则返回false
	 */
    public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }
	/** 
	 * 
	 * /
    public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        return tryAcquireShared(arg) >= 0 ||
            doAcquireSharedNanos(arg, nanosTimeout);
    }
	/** 
	 * 
	 * /
    private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
            throws InterruptedException {
            //时间小于0,则立即超时
        if (nanosTimeout <= 0L)
            return false;
            //获取超时的dealline
        final long deadline = System.nanoTime() + nanosTimeout;
        //加入AQS队列
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (;;) {
            	//获取前驱节点,如果前驱为head节点,则尝试获取共享锁,获取成功,
            	//则设置当前节点为头结点,并且传播释放下一个节点
                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 true;
                    }
                }
                nanosTimeout = deadline - System.nanoTime();
                //超时时间到达, 当超时时间到达的时候,并不会向后传播,而是
                if (nanosTimeout <= 0L)
                    return false;
                    //获取共享锁失败,判断是否需要挂起,只有当现在距离最后期限大于1微秒的时候,	 
                    //才会被挂起(挂起时间即为当前时间到dealline的时间间隔)
                if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

4. countDown()方法实现

countDown的比较简单,主要是将计数器值递减,如果计数器值已归零,那么会唤醒头结点的线程,让线程重新恢复运行。头结点被唤醒后,会调用setHeadAndPropagate向后传递唤醒AQS队列中的其他线程。以此实现处于await状态的线程全部都被唤醒。

	/** 
	 * 递减计数器值,当计数器值为0的时候,释放所有处于等待状态的线程
	 * /
    public void countDown() {
        sync.releaseShared(1);
    }
/**
* 释放共享锁,当计数器为0的时候返回true
* /
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }
   /**
     * 共享模式的释放,发信号给后继节点并确保能向后传播
     */
    private void doReleaseShared() {
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                // 如果当前节点是 SIGNAL 意味着,它正在等待一个信号,或者说,它在等待被唤醒,
                //因此做两件事,1 是重置 waitStatus 标志位,2 是重置成功后, 唤醒下一个节点
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }
               // 如果本身头节点的 waitStatus 是出于重置状态(waitStatus==0)的,
               //将其设置为“传播”状态。意味着需要将状态向后一个节点传播。
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值