countdownlatch学习

countdownlcath

定义

CountDownLatch其实是复合名词,由单词countdown和latch复合而来。countdown是倒数的意思,而latch则是闩锁、闭锁的意思,复合词容易让人联想到预先设定一个计数值,并且"锁住(阻塞)“一些东西(线程),然后进行倒数,当数值减少到0的时候进行"放行(解除阻塞)”。

基本使用

// 构造函数,要求初始的计数值要大于零
public CountDownLatch(int count) ......

// 当前调用线程会等待直到计数值倒数为0或者线程中断
public void await() throws InterruptedException ......

// 当前调用线程会等待直到计数值倒数为0、线程中断或者超过输入的等待时间期限
public boolean await(long timeout, TimeUnit unit) throws InterruptedException ......

// 计数值减少1,当计数值减少到0所有等待线程会释放(解除等待)
public void countDown() ......

// 获取计数值
public long getCount() ......

示例
    @Test
    public void uploadTs() throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        CountDownLatch count = new CountDownLatch(10);
        for (int i = 0; i < 10; i++) {

            executorService.execute(new MyCount(count, i));
        }
    }


    class MyCount implements Runnable {

        private final CountDownLatch countDown;
        private final int i;

        MyCount(CountDownLatch countDown1, int i1) {
            this.countDown = countDown1;
            this.i = i1;
        }
        public void run() {
            countDown.countDown();
            System.out.println(" this thread " + i);
        }
    }

原理

在之前的aqs中 有说过 coundownlatch是通过aqs来实现的 现在来看下它到底是怎么实现的,顺便回顾下aqs

public class CountDownLatch {

// 继承aqs 对指定的方法重写,复用sqs框架的锁机制
    private static final class Sync extends AbstractQueuedSynchronizer {

        private static final long serialVersionUID = 4982264981922014374L;
        
        // 设置AQS的state的值为输入的计数值
        Sync(int count) {
            setState(count);
        }
        
        // 获取AQS中的state属性
        int getCount() {
            return getState();
        }
        
        // 共享模式下获取资源,这里无视共享模式下需要获取的资源数,只判断当前的state值是否为0,为0的时候,意味资源获取成功,闭锁已经释放,所有等待线程需要解除阻塞
        // 如果state当前已经为0,那么线程完全不会加入AQS同步队列中等待,表现为直接运行
        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }
        
        // 共享模式下释放资源,这里也无视共享模式下需要释放的资源数,每次让状态值通过CAS减少1,当减少到0的时候,返回true
        protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
                int c = getState();
                // 这种情况下说明了当前state为0,从tryAcquireShared方法来看,线程不会加入AQS同步队列进行阻塞,所以也无须释放
                if (c == 0)
                    return false;
                // state的快照值减少1,并且通过CAS设置快照值更新为state,如果state减少为0则返回true,意味着需要唤醒阻塞线程
                int nextc = c - 1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }
     
    // 输入的计数值不能小于0,意味着AQS的state属性必须大于等于0
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

    public void await() throws InterruptedException {
        // 共享模式下获取资源,响应中断
        sync.acquireSharedInterruptibly(1);
    }

    public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        // 共享模式下获取资源,响应中断,带超时期限
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }

    public void countDown() {
        // 共享模式下释放资源
        sync.releaseShared(1);
    }

    public long getCount() {
        // 获取当前state的值
        return sync.getCount();
    }

    public String toString() {
        return super.toString() + "[Count = " + sync.getCount() + "]";
    }
}

countDown

public class CountDownLatch {

    private static final class Sync extends AbstractQueuedSynchronizer {

        private static final long serialVersionUID = 4982264981922014374L;
        
        // 设置AQS的state的值为输入的计数值
        Sync(int count) {
            setState(count);
        }
        
        // 获取AQS中的state属性
        int getCount() {
            return getState();
        }
        
        // 共享模式下获取资源,这里无视共享模式下需要获取的资源数,只判断当前的state值是否为0,为0的时候,意味资源获取成功,闭锁已经释放,所有等待线程需要解除阻塞
        // 如果state当前已经为0,那么线程完全不会加入AQS同步队列中等待,表现为直接运行
        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }
        
        // 共享模式下释放资源,这里也无视共享模式下需要释放的资源数,每次让状态值通过CAS减少1,当减少到0的时候,返回true
        protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
                int c = getState();
                // 这种情况下说明了当前state为0,从tryAcquireShared方法来看,线程不会加入AQS同步队列进行阻塞,所以也无须释放
                if (c == 0)
                    return false;
                // state的快照值减少1,并且通过CAS设置快照值更新为state,如果state减少为0则返回true,意味着需要唤醒阻塞线程
                int nextc = c - 1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }
     
    // 输入的计数值不能小于0,意味着AQS的state属性必须大于等于0
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

    public void await() throws InterruptedException {
        // 共享模式下获取资源,响应中断
        sync.acquireSharedInterruptibly(1);
    }

    public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        // 共享模式下获取资源,响应中断,带超时期限
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }

    public void countDown() {
        // 共享模式下释放资源
        sync.releaseShared(1);
    }

    public long getCount() {
        // 获取当前state的值
        return sync.getCount();
    }

    public String toString() {
        return super.toString() + "[Count = " + sync.getCount() + "]";
    }
}
接下来再分步解析每一个方法。先看构造函数:

// 构造函数,其实就是对AQS的state进行赋值
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 {

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

    // ......
}

// AbstractQueuedSynchronizer中
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {

    // ......
    
    // volatile修饰的状态值,变更会强制写回主内存,以便多线程环境下可见
    private volatile int state;

    // 调用的是这个父类方法
    protected final void setState(int newState) {
        state = newState;
    }

    // ......
}


由于AQS的头尾节点都是懒创建的,所以只初始化了state的情况下,AQS是"空的"。接着看await()方法:

// CountDownLatch中
public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

↓↓↓↓↓↓↓↓↓↓↓↓
// 私有静态内部类Sync中
private static final class Sync extends AbstractQueuedSynchronizer {
    
    // state等于0的时候返回1,大于0的时候返回-1
    protected int tryAcquireShared(int acquires) {
        return (getState() == 0) ? 1 : -1;
    }

    // ......
}

↓↓↓↓↓↓↓↓↓↓↓↓
// AbstractQueuedSynchronizer中
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {

    // ......
    
    // 共享模式下获取资源,响应中断
    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        // 如果线程已经处于中断状态,则清空中断状态位,抛出InterruptedException
        if (Thread.interrupted())
            throw new InterruptedException();
        // 尝试获取资源,此方法由子类CountDownLatch中的Sync实现,小于0的时候,说明state > 0
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }

    // ......

    //
    private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
        // 基于当前线程新建一个标记为共享的新节点
        final Node node = addWaiter(Node.SHARED);
        try {
            for (;;) {
                // 获取新入队节点的前驱节点
                final Node p = node.predecessor();
                // 前驱节点为头节点
                if (p == head) {
                    // 并且尝试获取资源成功,也就是每一轮循环都会调用tryAcquireShared尝试获取资源(r >= 0意味获取成功),除非阻塞或者跳出循环
                    // 由前文可知,CountDownLatch中只有当state = 0的情况下,r才会大于等于0
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        // 设置头结点,并且传播获取资源成功的状态,这个方法的作用是确保唤醒状态传播到所有的后继节点
                        // 然后任意一个节点晋升为头节点都会唤醒其第一个有效的后继节点,起到一个链式释放和解除阻塞的动作
                        setHeadAndPropagate(node, r);
                        // 由于节点晋升,原来的位置需要断开,置为NULL便于GC
                        p.next = null; // help GC
                        return;
                    }
                }
                // shouldParkAfterFailedAcquire ->  判断获取资源失败是否需要阻塞,这里会把前驱节点的等待状态CAS更新为Node.SIGNAL
                // parkAndCheckInterrupt -> 判断到获取资源失败并且需要阻塞,调用LockSupport.park()阻塞节点中的线程实例,(解除阻塞后)清空中断状态位并且返回该中断状态
                if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } catch (Throwable t) {
            cancelAcquire(node);
            throw t;
        }
    }

    // ......
}
因为是工程化的代码,并且引入了死循环避免竞态条件下的异常,代码看起来比较复杂,其实做了下面几件事:

先验当前调用线程的中断状态。
state不为0的时候,当前调用线程实例会包装为一个SHARED类型的Node加入AQS的同步等待队列的尾部,并且通过LockSupport.park()阻塞节点中的线程实例。
state为0的时候,直接返回,放行线程(可以尝试使用new CountDownLatch(0))。
死循环中如果tryAcquireShared(arg)返回值大于等于0,则任意一个晋升为头节点的节点解除阻塞后都会链式唤醒后继(正常的)节点,唤醒的逻辑在setHeadAndPropagate()方法中,这个方法命名其实有点奇怪,直译为"设置头节点和传播"。
细心一看,唤不唤同步等待队列中的阻塞节点,只取决于tryAcquireShared(arg)方法的返回值是否大于0,往前一推敲,取决于state或者说count是否为0,所以可知必定有使state变小的方法



接着看countDown()方法:

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

↓↓↓↓↓↓↓↓↓↓↓↓
// 私有静态内部类Sync中
private static final class Sync extends AbstractQueuedSynchronizer {
    
    // 共享模式下释放资源,这里也无视共享模式下需要释放的资源数,每次让状态值通过CAS减少1,当减少到0的时候,返回true
    protected boolean tryReleaseShared(int releases) {
        // 减少计数值state,直到变为0,则进行释放
        for (;;) {
            int c = getState();
            // 如果已经为0,直接返回false,不能再递减到小于0,返回false也意味着不会进入AQS的doReleaseShared()逻辑
            if (c == 0)
                return false;
            int nextc = c - 1;
            // CAS原子更新state = state - 1
            if (compareAndSetState(c, nextc))
                // 如果此次递减为0则返回true
                return nextc == 0;
        }
    }

    // ......
}

↓↓↓↓↓↓↓↓↓↓↓↓
// AbstractQueuedSynchronizer中
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
     
    // ......
    
    // 共享模式下,释放arg个资源
    public final boolean releaseShared(int arg) {
        // 从上面的分析来看,这里只有一种可能返回true并且进入doReleaseShared()方法,就是state由1递减为0的时候
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }
    
    // 共享模式下的释放操作
    private void doReleaseShared() {
        // 死循环是避免因为新节点入队产生影响,CAS做状态设置被放在死循环中失败了会在下一轮循环中重试
        for (;;) {
            Node h = head;
            // 头不等于尾,也就是AQS同步等待队列不为空
            // h == NULL,说明AQS同步等待队列刚进行了初始化,并未有持有线程实例的节点
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                // 头节点为Node.SIGNAL(-1),也就是后继节点需要唤醒,CAS设置头节点状态-1 -> 0,并且唤醒头节点的后继节点(也就是紧挨着头节点后的第一个节点)
                if (ws == Node.SIGNAL) {
                    // 这个if分支是对于Node.SIGNAL状态的头节点,这种情况下,说明
                    // 这里使用CAS的原因是setHeadAndPropagate()方法和releaseXX()方法都会调用此doReleaseShared()方法,CAS也是并发控制的一种手段
                    // 如果CAS失败,很大可能是头节点发生了变更,需要进入下一轮循环更变头节点的引用再进行判断
                    // 该状态一定是由后继节点为当前节点设置的,具体见shouldParkAfterFailedAcquire()方法
                    if (!h.compareAndSetWaitStatus(Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    // 唤醒后继节点,如果有后继节点被唤醒,则后继节点会调用setHeadAndPropagate()方法,更变头节点和转播唤醒状态
                    unparkSuccessor(h);
                }
                // 头节点状态为0,说明头节点的后继节点未设置前驱节点的waitStatus为SIGNAL,代表无需唤醒
                // CAS更新它的状态0 -> Node.PROPAGATE(-3),这个标识目的是为了把节点状态设置为跟Node.SIGNAL(-1)一样的负数值,
                // 便于某个后继节点解除阻塞后,在一轮doAcquireSharedInterruptibly()循环中调用shouldParkAfterFailedAcquire()方法返回false,实现"链式唤醒"
                else if (ws == 0 && !h.compareAndSetWaitStatus(0, Node.PROPAGATE))  
                    continue;                // loop on failed CAS
            }
            // 如果头节点未发生变化,则代表当前没有其他线程获取到资源,晋升为头节点,直接退出循环
            // 如果头节点已经发生变化,代表已经有线程(后继节点)获取到资源,
            if (h == head)                   // loop if head changed
                break;
        }
    }

    // 解除传入节点的第一个后继节点的阻塞状态,当前处理节点的等待状态会被CAS更新为0
    private void unparkSuccessor(Node node) {
        // 当前处理的节点状态小于0则直接CAS更新为0
        int ws = node.waitStatus;
        if (ws < 0)
            node.compareAndSetWaitStatus(ws, 0);
        // 如果节点的第一个后继节点为null或者等待状态大于0(取消),则从等待队列的尾节点向前遍历,
        // 找到最后一个(这里指的是队列尾部->队列头部搜索路径的最后一个满足的节点,一般是传入的node节点的next节点)不为null,并且等待状态小于等于0的节点
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node p = tail; p != node && p != null; p = p.prev)
                if (p.waitStatus <= 0)
                    s = p;
        }
        // 解除传入节点的后继节点的阻塞状态,唤醒后继节点所存放的线程
        if (s != null)
            LockSupport.unpark(s.thread);
    }
    // ......
}

await
// CountDownLatch中
public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

↓↓↓↓↓↓↓↓↓↓↓↓
// 私有静态内部类Sync中
private static final class Sync extends AbstractQueuedSynchronizer {
    
    // state等于0的时候返回1,大于0的时候返回-1
    protected int tryAcquireShared(int acquires) {
        return (getState() == 0) ? 1 : -1;
    }

    // ......
}

↓↓↓↓↓↓↓↓↓↓↓↓
// AbstractQueuedSynchronizer中
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {

    // ......
    
    // 共享模式下获取资源,响应中断
    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        // 如果线程已经处于中断状态,则清空中断状态位,抛出InterruptedException
        if (Thread.interrupted())
            throw new InterruptedException();
        // 尝试获取资源,此方法由子类CountDownLatch中的Sync实现,小于0的时候,说明state > 0
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }

    // ......

    //
    private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
        // 基于当前线程新建一个标记为共享的新节点
        final Node node = addWaiter(Node.SHARED);
        try {
            for (;;) {
                // 获取新入队节点的前驱节点
                final Node p = node.predecessor();
                // 前驱节点为头节点
                if (p == head) {
                    // 并且尝试获取资源成功,也就是每一轮循环都会调用tryAcquireShared尝试获取资源(r >= 0意味获取成功),除非阻塞或者跳出循环
                    // 由前文可知,CountDownLatch中只有当state = 0的情况下,r才会大于等于0
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        // 设置头结点,并且传播获取资源成功的状态,这个方法的作用是确保唤醒状态传播到所有的后继节点
                        // 然后任意一个节点晋升为头节点都会唤醒其第一个有效的后继节点,起到一个链式释放和解除阻塞的动作
                        setHeadAndPropagate(node, r);
                        // 由于节点晋升,原来的位置需要断开,置为NULL便于GC
                        p.next = null; // help GC
                        return;
                    }
                }
                // shouldParkAfterFailedAcquire ->  判断获取资源失败是否需要阻塞,这里会把前驱节点的等待状态CAS更新为Node.SIGNAL
                // parkAndCheckInterrupt -> 判断到获取资源失败并且需要阻塞,调用LockSupport.park()阻塞节点中的线程实例,(解除阻塞后)清空中断状态位并且返回该中断状态
                if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } catch (Throwable t) {
            cancelAcquire(node);
            throw t;
        }
    }
    ```
 -   先验当前调用线程的中断状态。
- state不为0的时候,当前调用线程实例会包装为一个SHARED类型的Node加入AQS的同步等待队列的尾部,并且通过LockSupport.park()阻塞节点中的线程实例。
- state为0的时候,直接返回,放行线程(可以尝试使用new CountDownLatch(0))。
- 死循环中如果tryAcquireShared(arg)返回值大于等于0,则任意一个晋升为头节点的节点解除阻塞后都会链式唤醒后继(正常的)节点,唤醒的逻辑在setHeadAndPropagate()方法中






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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值