侧边栏壁纸
博主头像
敢敢雷博主等级

永言配命,自求多福

  • 累计撰写 57 篇文章
  • 累计创建 0 个标签
  • 累计收到 2 条评论

目 录CONTENT

文章目录

等待多线程完成的CountDownLatch

敢敢雷
2020-02-02 / 0 评论 / 0 点赞 / 109 阅读 / 1,345 字
温馨提示:
部分素材来自网络,若不小心影响到您的利益,请联系我删除。

CountDownLatch允许一个或者多个线程等待其他线程完成操作。
先看CountDownLatch一个简单小Demo

Demo

简单一点,以英雄联盟开五黑为例子,五黑需要五个人才能发车。代码如下

public class CountDownLatchDemo {
    public static void main(String[] args) {
        CountDownLatch latch = new CountDownLatch(5);
        new Thread(()->{
            // 摇人整活
            System.out.println("赶紧摇人来整活");
            try {
                // 憨憨等人上号
                latch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 开始游戏
            System.out.println("发车!!!!!");
        }).start();
	// 循环创建模拟五个人上号的过程
        for (int i = 0; i < 5 ; i++) {
            new Thread(()->{
                // 憨憨上号中。。。
                System.out.println(Thread.currentThread().getName()+"正在上号");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 憨憨上号成功 count - 1
                System.out.println(Thread.currentThread().getName()+"已上号,等待发车");
                latch.countDown();
            },"第"+(i+1)+"个人").start();
        }
    }
}

具体运行结果如下:
image.png

可以看见运行结果,运行结果是其他五个线程全部执行完后,await的线程的方法才能继续执行。接下来就研究下CountDownLatch的原理

CountDownLatch介绍

CountDownLatch所描述的是”在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待“。

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

CountDownLatch是通过一个计数器来实现的,当我们在new 一个CountDownLatch对象的时候需要带入该计数器值,该值就表示了线程的数量。每当一个线程完成自己的任务后,计数器的值就会减1。当计数器的值变为0时,就表示所有的线程均已经完成了任务,然后就可以恢复等待的线程继续执行了。

CountDownLatch latch = new CountDownLatch(5);

如上述代码,即计数器值为5。

CountDownLatch原理解析

CountDownLatch结构

image.png

CountDownLatch内部依赖Sync实现,而Sync继承AQS。

CountDownLatch构造方法

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

方法首先判断了count的值,然后构造其Sync对象,查看Sync类

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

通过内部类Sync我们可以知道CountDownLatch是采用共享锁来实现的。

CountDownLatch.await()方法

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

进入acquireSharedInterruptibly方法

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

该方法是使用的AQS的acquireSharedInterruptibly(int arg);其作用是共享式获取同步状态,响应中断。
同时,内部类Sync重写了tryAcquireShared方法。

 protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

getState()获取同步状态,其值等于计数器的值,从这里我们可以看到如果计数器值不等于0,则会调用doAcquireSharedInterruptibly(int arg),该方法为一个自旋方法会尝试一直去获取同步状态

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) {
                    /**
                     * 对于CountDownLatch而言,如果计数器值不等于0,那么r 会一直小于0
                     */
                    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);
        }
    }

CountDownLatch.countDown()方法

CountDownLatch提供countDown() 方法递减锁存器的计数,如果计数到达零,则释放所有等待的线程。

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

内部调用AQS的releaseShared(int arg)方法来释放共享锁同步状态:

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

内部类Sync重写了tryReleaseShared方法。

protected boolean tryReleaseShared(int releases) {
        for (;;) {
            //获取锁状态
            int c = getState();
            //c == 0 直接返回,释放锁成功
            if (c == 0)
                return false;
            //计算新“锁计数器”
            int nextc = c-1;
            //更新锁状态(计数器)
            if (compareAndSetState(c, nextc))
                return nextc == 0;
        }
    }

总结

  1. CountDownLatch内部通过共享锁实现。
  2. 在创建CountDownLatch实例时,需要传递一个int型的参数:count,该参数为计数器的初始值,也可以理解为该共享锁可以获取的总次数。
  3. 当某个线程调用await()方法,程序首先判断count的值是否为0,如果不会0的话则会一直等待直到为0为止。当其他线程调用countDown()方法时,则执行释放共享锁状态,使count值 - 1。
  4. 当在创建CountDownLatch时初始化的count参数,必须要有count线程调用countDown方法才会使计数器count等于0,锁才会释放,前面等待的线程才会继续运行。
  5. CountDownLatch不能回滚重置。
0

评论区