浅析JUC-CountDownLatch

JUC-CountDownLatch详解

CountDownLatch是通过AQS实现的同步器,与ReentrantLock不同的是,CountDownLatch实现的是共享式获取锁的方法,而非是独占式的。也就是说不同的线程都可以调用CountDownLatch方法获取锁,或者对锁进行释放

CountDownLatch源码

下面我们来看看其源码:

/*
CountCDownLatch是一种同步辅助工具,允许一个或者多个线程等待一组在其他线程中执行的操作完成

CountDownLatch在初始化时会指定一个数值num,在num达到0之前await方法将一直阻塞。
我们可以调用countDown方法将num减1。之后会释放所有等待的线程,并立即返回await方法的后续调用。
CountDownLatch的计数器无法重置,如果需要能够重置计数的版本可以使用CyclicBarrier

CountDownLatch是一种通用的同步工具,有多种用途。
可以用一个计数初始化CountDownLatch充当一个简单的开关或者门:
所有调用它的线程都必须在门外等待,直到它被调用CountDownLatch的线程打开
初始化为N的CountDownLatch可用于使一个线程等待N个线程完成某个操作,或者某个操作已经完成N次了


简单的用法:
class Driver { // ...
   void main() throws InterruptedException {
     CountDownLatch startSignal = new CountDownLatch(1);
     CountDownLatch doneSignal = new CountDownLatch(N);

     for (int i = 0; i < N; ++i) // create and start threads
       new Thread(new Worker(startSignal, doneSignal)).start();

     doSomethingElse();            // don't let run yet
     startSignal.countDown();      // let all threads proceed
     doSomethingElse();
     doneSignal.await();           // wait for all to finish
   }
 }

 class Worker implements Runnable {
   private final CountDownLatch startSignal;
   private final CountDownLatch doneSignal;
   Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
     this.startSignal = startSignal;
     this.doneSignal = doneSignal;
   }
   public void run() {
     try {
       startSignal.await();
       doWork();
       doneSignal.countDown();
     } catch (InterruptedException ex) {} // return;
   }

   void doWork() { ... }
 }
*/
public class CountDownLatch {
    /*
    	通过继承AQS实现了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();
        }
		// 实现了tryAcquireShared方法,意味着这是共享锁,不同的线程可以同时获取锁
        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }
		// 实现了tryReleaseShared方法,共享的释放锁
        protected boolean tryReleaseShared(int releases) {
            // 释放锁
            for (;;) {
                int c = getState();
                // 如果为0证明不需要释放锁
                if (c == 0)
                    return false;
                // 继续进行state--操作,并使用cas方法设置state的值
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    // 判断释放后的state是否为0,为0则返回true,不为0则继续释放
                    return nextc == 0;
            }
        }
    }
	// CountDownLatch的字段,通过Sync实现了同步控制
    private final Sync sync;
	// 构造方法,通过设置初始的count值,count意味着目前有多少线程在该同步器上等待
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }
    /*
    该方法会导致调用的线程处于等待状态,直到count减为0,或者线程被中断
    如果count为0该方法就立即返回
    如果count大于0,那么当前线程就不能被调度并且会休眠,直到以下两件事发生为止:
    1、调用countDown方法使count减为0,或者
    2、其他线程中断该线程
    */
    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
    /*
    指定等待时间的await方法
    如果经过指定的等待时间后,count依旧没有变为0,那么就返回false
    如果在此期间count达到0,那么就返回true
    */
    public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }
    /*
    将count减1,也就是AQS的state-1,如果count达到0就释放所有的等待线程
    如果当前计数大于0,则将它递减
    如果新计数为0,则将所有等待的线程重新启动,以用于线程调度
    */
    public void countDown() {
        sync.releaseShared(1);
    }
    /*
    返回当前的count
    这个方法通常是用来调试的
    */
    public long getCount() {
        return sync.getCount();
    }
    
    public String toString() {
        return super.toString() + "[Count = " + sync.getCount() + "]";
    }
}

CountDownLatch作用

CountDownLatch的主要作用就是做多线程的汇聚,当主线程开启了A、B、C三个线程做不同的事情时,主线程需要等待A、B、C三个线程全部完成之后才能继续后面的步骤,此时就能够用到CountDownLatch了。CountDownLatch会阻塞主线程,直到A、B、C三个线程运行完成以后才会唤醒主线程。

我们可以通过流程图看看它的内容:

image-20210414105135439

简单使用

如果想让主线程等待线程1和2运行完成之后再运行,可以使用CountDownLatch来完成这个目标,例如:

	public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(2);
        System.out.println("主线程开始等待线程1和线程2");
        new Thread(() -> {
            System.out.println("线程1运行");
            countDownLatch.countDown();
        }).start();

        new Thread(() -> {
            System.out.println("线程2运行");
            countDownLatch.countDown();
        }).start();
        countDownLatch.await();

        System.out.println("主线程等待完毕,开始运行");
    }

总结

通过CountDownLatch的源码,我们发现了它与ReentrantLock相近的地方,都实现了AQS的接口,但区别在于CountDownLatch是共享模式而ReentrantLock是独占模式,这两种同步器都有各自的用处。

ReentrantLock是可重入锁,但是这是对单个线程而言的,也就是我们可以对与已经加锁的线程再次进行加锁

CountDownLatch是一种用于多个线程相互等待,直到等待的所有线程运行完毕才执行等待这些线程的线程

在实际应用中我们可以根据需要选择使用哪种同步器

从这两个源码我们也发现了AQS的重要作用,CountDownLatch和ReentrantLock都是基于AQS实现的,所以我们要好好了解和掌握AQS的原理,以后也可以根据业务需要写出适合自己业务的同步器。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值