并发测试辅助类CountDownLatch使用与源码

  • CountDownLatch类介绍:

    一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。

用给定的计数 初始化 CountDownLatch。由于调用了 countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier。

CountDownLatch 是一个通用同步工具,它有很多用途。将计数 1 初始化的 CountDownLatch 用作一个简单的开/关锁存器,或入口:在通过调用 countDown() 的线程打开入口前,所有调用 await 的线程都一直在入口处等待。用 N 初始化的 CountDownLatch 可以使一个线程在 N 个线程完成某项操作之前一直等待,或者使其在某项操作完成 N 次之前一直等待。

CountDownLatch 的一个有用特性是,它不要求调用 countDown 方法的线程等到计数到达零时才继续,而在所有线程都能通过之前,它只是阻止任何线程继续通过一个 await。

await()方法:
throws InterruptedException
使当前线程在锁存器倒计数至零之前一直等待,除非线程被 中断。
如果当前计数为零,则此方法立即返回。

如果当前计数大于零,则出于线程调度目的,将禁用当前线程,且在发生以下两种情况之一前,该线程将一直处于休眠状态:

由于调用 countDown() 方法,计数到达零;或者
其他某个线程中断当前线程。

countDown()方法:
递减锁存器的计数,如果计数到达零,则释放所有等待的线程。
如果当前计数大于零,则将计数减少。如果新的计数为零,出于线程调度目的,将重新启用所有的等待线程。

如果当前计数等于零,则不发生任何操作。

综合上述所的:也就是当countDown()方法的值减为0的时候,取消阻塞;否则调用await()会被一只阻塞,下面是测试代码:

扫描二维码关注公众号,回复: 3274545 查看本文章
package com.example.demo;/**
 * Created by wusong on 18/8/27.
 */

import java.util.concurrent.CountDownLatch;

public class Etest implements Runnable {

    static final CountDownLatch startSignal = new CountDownLatch(1);//拉粑粑信号枪
    static final CountDownLatch donesSignal = new CountDownLatch(10);//排队拉粑粑的人
    static int k =100;



    @Override
    public void run() {
        try {
            startSignal.await();//把拉粑粑的人堵在厕所门口,直至countDown为0
            System.out.println("我是"+Thread.currentThread().getName()+"我抢到了拉粑粑权,很开心!");
            donesSignal.countDown();//哦耶,我拉完了。撤了~
            System.out.println("我是"+Thread.currentThread().getName()+"我拉完了");

        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    public static void main(String[] args) throws Exception {
        //创建憋粑粑的人
        for (int i = 0;i < 10;i++)
            new Thread(new Etest(),"拉粑粑能手 NO."+(1+i)).start();
        startSignal.countDown();//把所有被堵在厕所门口要拉粑粑的人放出来~
        donesSignal.await();
    }

}

然后我们来看这个类是如何具体实现对所有线程进行阻塞以方便我们测试的;
首先是构造函数:

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

一只点点点,最后到了AbstractQueuedSynchronizer类中的setState方法(依赖AQS实现的)将我们上述传入的count设置为了state

    protected final void setState(int newState) {
        state = newState;
    }
  • 先来看countDown()方法:
    public void countDown() {
        sync.releaseShared(1);
    }

AbstractQueuedSynchronizer类中的releaseShared方法:

    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

CountDownLatch具体实现的tryReleaseShared(arg):

        protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
                int c = getState();
                //为0返回false
                if (c == 0)
                    return false;
                    //每调用一次减1
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
  • 再来看await()方法,也就是为啥能阻塞俺们所有的线程:
    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

点进去来到了AbstractQueuedSynchronizer类中的,这是我们就发现这玩意是基于共享锁实现的,搜呆斯内:

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

tryAcquireShared(arg) 的具体实现:

protected int tryAcquireShared(int acquires) {
			//getState()就是我们之前构造函数设置的值,如果减为0的话返回1否则返回-1
            return (getState() == 0) ? 1 : -1;
        }

然后来到doAcquireSharedInterruptibly(int arg),参数arg为1:

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) {
                //重复之前的操作,如果之前的操作,这时候我们就回判断state是否为0,
                //以为同时也会有其他线程执行countDown()方法,为了保证原子性的操作得从新获取一次。
                    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);
        }
    }

具体的释放操作:

    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())
                doReleaseShared();
        }
    }

猜你喜欢

转载自blog.csdn.net/qq_36866808/article/details/82763021