【Java并发】- 5.对并发工具类CountDownLatch的源码解析

1.简介

CountDownLatch允许一个或多个线程等待其他线程完成操作。

与thread的join方法的实现的功能相似,不过join用于让当前执行线程等待join线程执行结束。其实现原理是不停检查join线程是否存活,如果join线程存活则让当前线程永远等待。

与join不同CountDownLatch可以让开发者自行定义线程执行的位置。CountDownLatch的构造函数接收一个int类型的参数作为计数器,如果你想等待N个点完成,这里就传入N。当我们调用CountDownLatch的countDown方法时,N就会减1,CountDownLatch的await方法会阻塞当前线程,直到N变成零。由于countDown方法可以用在任何地方,所以这里说的N个点,可以是N个线程,也可以是1个线程里的N个执行步骤。用在多个线程时,只需要把这个CountDownLatch的引用传递到线程里即可。

故CountDownLatch使用起来比join方法更加灵活。

注意 计数器必须大于等于0,只是等于0时候,计数器就是零,调用await方法时不会
阻塞当前线程。CountDownLatch不可能重新初始化或者修改CountDownLatch对象的
内部计数器的值。一个线程调用countDown方法happen-before,另外一个线程调用await方法。

2.如何使用CountDownLatch

public class CountDownLatchDemo {

    public static void main(String[] args) {
        CountDownLatch countDownLatch = new CountDownLatch(3);

        IntStream.range(0,3).forEach(i -> new Thread(() -> {
            try {
                Thread.sleep(2000);
                System.out.println("hello");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                countDownLatch.countDown();
            }
        }).start());

        System.out.println("子线程执行完毕");

        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程执行完毕");
    }
}

执行的结果

子线程执行完毕
hello
hello
hello
主线程执行完毕

可以看到,我们在CountDownLatch中放入了计算器3,而当3个线程都执行完后才执行的await方法。这样如果在编程中遇到,一个线程执行的前置条件时必须其他几个线程执行到某一步时,使用join方法肯定是不行的,此时就可以使用CountDownLatch来实现线程的等待。

不过上述使用CountDownLatch存在很大的问题,如果计算器与执行countDownLatch.countDown();的方法个数不一致就会导致,计算器不能到达0,使得await一直死循环。为了解决这种问题出现了await方法的变种方法await(long timeout, TimeUnit unit)这个方法设置了等待时间,如果到了等待时间计算器还不为0就会自动唤醒await的线程,防止线程出现死循环的情况

public class CountDownLatchDemo {

    public static void main(String[] args) {
        CountDownLatch countDownLatch = new CountDownLatch(3);

        IntStream.range(0,3).forEach(i -> new Thread(() -> {
            try {
                System.out.println("thread" + i);
                Thread.sleep(2000);
                System.out.println("hello");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                countDownLatch.countDown();
            }
        }).start());

        System.out.println("子线程执行完毕");

        try {
            countDownLatch.await(1000,TimeUnit.MICROSECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程执行完毕");
    }
}

程序执行结果

子线程执行完毕
thread0
thread1
thread2
主线程执行完毕
hello
hello
hello

3.对CountDownLatch类中方法的解析

CountDownLatch中包含如下的方法
在这里插入图片描述
下面对其中几个重要的方法进行解析。

构造方法

分析可得CountDownLatch只有一个有参构造方法

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

构造方法中,

  • 第一步对传入的值进行检验,(因为这是一个减的计算器,所以count必须大于0)
  • 二:声明了一个CountDownLatch类的的私有类Sync
 private static final class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 4982264981922014374L;

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

可知道Sync类是一个AQS实现类。所以CountDownLatch的底层是基于AQS实现的

    protected final void setState(int newState) {
        state = newState;
    }

其最终执行到setState方法设置了计数器的值。

await方法
public void await() throws InterruptedException {
//执行了抽象类(准确的说是sync类继承但没有重写的方法)中的方法
//至于传入的参数1,在此处没有实际意义,正式在AQS中规定这样传参
   sync.acquireSharedInterruptibly(1);
}

//抽象类AbstractQueuedSynchronizer的方法
public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        //判断线程是否被打断
        if (Thread.interrupted())
            throw new InterruptedException();
        //
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }
//抽象类AbstractQueuedSynchronizer的方法
//这个方法没有具体实现,所以应该在其实现类中找其重写的方法
protected int tryAcquireShared(int arg) {
        throw new UnsupportedOperationException();
    }
//CountDownLatch内部类Sync的方法
protected int tryAcquireShared(int acquires) {
//这是判断在CountDownLatch构造方法传入的计数器是否为0.参数被没有被使用。
//如果返回负数acquireSharedInterruptibly方法会进入doAcquireSharedInterruptibly
//等待计数器的值变为0,如果返回正数则acquireSharedInterruptibly通过执行
//即CountDownLatch中计数器值为0,可以唤醒主线程执行
            return (getState() == 0) ? 1 : -1;
        }

await方法会在CountDownLatch中的计数器不为0之前一直等待,直到计数器为0,才会唤醒执行await方法的线程,当然线程也有可能被打的而唤醒,然后抛出InterruptedException 异常。

所以await方法的逻辑很简单,就是判断CountDownLatch中计数器的值算法为0,

  • 不为0就把线程放入等待队列
  • 为0,则线程继续然后执行
await方法的变种方法await(long timeout, TimeUnit unit)
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);
    }

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

和await方法的逻辑类似,只是在*doAcquireSharedNanos(arg, nanosTimeout);*方法中做了超时唤醒的处理。这个方法是由AQS具体实现,故在分析AQS代码时在具体分析。

即如果到达超时时间,CountDowmLatch中的计数器还没有归0,执行await的方法也照常唤醒

countDown()方法
public void countDown() {
        sync.releaseShared(1);
    }
//抽象类AbstractQueuedSynchronizer的方法
public final boolean releaseShared(int arg) {
//判断tryReleaseShared返回,如果为真则通过doReleaseShared唤醒所以等待的线程且返回true
//如果为假返回false不做任何处理
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }
//CountDownLatch内部类Sync的方法
protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
                int c = getState();
                //判断计数器(State)是否为0.因为如果计数器为0就不做等待操作
                //直接返回,且如果为0,说明之前必定有一个线程执行了
                //tryReleaseShared方法对计数器减一成功,故返回false
                if (c == 0)
                    return false;
                //通过CAS把c的值减一,再判断计数器是否为0,如果为0则返回true,唤醒
                //所以等待线程。负责返回false不做任何处理
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }

该方法的作用是对计数器进行减一,如果计数器的值变为0,则唤醒所有正在等待的线程。

执行该方法后CountDownLatch中计数器的值最终只可能>=0。

CountDownLatch类中其他方法没有分析的价值,故这里不做分析。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,您想了解关于Java并发编程中的CountDownLatch吗? CountDownLatchJava并发编程中非常重要的一个工具类,它可以让某个线程等待其他线程完成后再继续执行。它的作用类似于倒计时门闩,当count值减为0时,所有等待者都会被唤醒。 CountDownLatch是一次性的,也就是说,它的计数器只能使用一次。如果需要重复使用,可以考虑使用CyclicBarrier。 在使用CountDownLatch时,我们需要先创建一个CountDownLatch对象,并指定计数器的初始值。然后在需要等待的线程中调用await()方法进行等待,同时在其他线程中调用countDown()方法进行计数器的减1操作。 举个例子,假设我们有一个需求:主线程需要等待两个子线程完成后再执行。那么可以这样编写代码: ```java import java.util.concurrent.CountDownLatch; public class CountDownLatchDemo { public static void main(String[] args) throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(2); new Thread(() -> { System.out.println(Thread.currentThread().getName() + "执行开始"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "执行完毕"); countDownLatch.countDown(); }, "线程1").start(); new Thread(() -> { System.out.println(Thread.currentThread().getName() + "执行开始"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "执行完毕"); countDownLatch.countDown(); }, "线程2").start(); System.out.println(Thread.currentThread().getName() + "等待子线程执行完毕"); countDownLatch.await(); System.out.println(Thread.currentThread().getName() + "所有子线程执行完毕,继续执行主线程"); } } ``` 在上面的例子中,我们首先创建了一个计数器初始值为2的CountDownLatch对象,然后创建了两个线程分别进行一些操作,并在操作结束后调用countDown()方法进行计数器减1操作。在主线程中,我们调用await()方法进行等待,直到计数器减为0时,主线程才会继续执行。 希望能够对您有所帮助!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值