Java并发编程——AQS组件之CountDownLatch

一、AQS

1. AQS介绍:

  AQSAbstractQueuedSynchronizer),是并发容器J.U.C(java.lang.concurrent)locks包内的一个类。它实现了一个FIFO(FirstIn、FisrtOut先进先出)的队列。底层实现的数据结构是一个双向列表。

2. AQS组件:

  AQS组件包括:CountDownLatchSemaphoreCyclicBarrier

3. AQS的实现思路

  AQS内部维护了一个CLH队列来管理锁。线程会首先尝试获取锁,如果失败就将当前线程及等待状态等信息包装成一个node节点加入到同步队列sync queue里。接着会不断的循环尝试获取锁,条件是当前节点为head的直接后继才会尝试。如果失败就会阻塞自己直到自己被唤醒。而当持有锁的线程释放锁的时候,会唤醒队列中的后继线程。

4. AQS设计思想

  • 使用Node实现FIFO队列,可以用于构建锁或者其他同步装置的基础框架。
  • 利用int类型标识状态。在AQS类中有一个叫做state的成员变量。
    源码:
/**
 * The synchronization state.
 */
private volatile int state;
  • 基于AQS有一个同步组件,叫做ReentrantLock。在这个组件里,stste表示获取锁的线程数,假如state=0,表示还没有线程获取锁,1表示有线程获取了锁。大于1表示重入锁的数量。
  • 继承:子类通过继承并通过实现它的方法管理其状态(acquire和release方法操纵状态)。
  • 可以同时实现排它锁和共享锁模式(独占、共享),站在一个使用者的角度,AQS的功能主要分为两类:独占和共享。它的所有子类中,要么实现并使用了它的独占功能的api,要么使用了共享锁的功能,而不会同时使用两套api,即便是最有名的子类ReentrantReadWriteLock也是通过两个内部类读锁和写锁分别实现了两套api来实现的;

二、CountDownLatch组件

1. CountDownLatch介绍

  CountDownLatch是一种java.util.concurrent包下一个同步工具类,它允许一个或多个线程等待直到在其他线程中一组操作执行完成。CountDownLatch是基于AbstractQueuedSynchronizer(AQS)实现的,其通过state作为计数器。构造CountDownLatch时初始化一个state,以后每调用countDown()方法一次,state减1;当state=0时,唤醒在await()上被挂起的线程。
在这里插入图片描述

2. CountDownLatch的使用与分析

  • CountDownLatch countDownLatch= new CountDownLatch(N); //构造对象时候 需要传入参数N;**
  • countDownLatch.await() 能够阻塞线程 直到调用N次countDownLatch.countDown()方法才释放线程;**
  • countDownLatch.countDown() 可以在多个线程中调用 计算调用次数是所有线程调用次数的总和;**

总结来说,await()后面的方法,需要在countDown()方法中的计数器减到0后,才允许被执行

示例源码:

@Slf4j
public class CountDownLatchExample1 {

    private static final int threadCount = 200;

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final CountDownLatch countDownLatch = new CountDownLatch(threadCount);

		//此处循环将200次,因此 countDownLatch.countDown()也执行了200次
        for (int i=0; i<threadCount; i++){
            final int threadNum = i;
            executorService.execute(()->{
                try {
                    test(threadNum);
                }catch (Exception e){
                    log.error("exception",e);
                }finally {
                    countDownLatch.countDown();
                }
            });
        }
        countDownLatch.await();
        log.info("finish!!");
        executorService.shutdown();
    }

    private static void test(int threadNum) throws InterruptedException {
        Thread.sleep(100);
        log.info("{}",threadNum);
        Thread.sleep(100);
    }
}

上述源码演示的是:在线程池中启动200个线程,每个线程都是执行test()方法,期望在200个线程执行结束之后,打印“finish!!”字样;
执行结果如下所示:

...
19:59:45.018 [pool-1-thread-175] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - 174
19:59:45.018 [pool-1-thread-176] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - 175
19:59:45.018 [pool-1-thread-177] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - 176
19:59:45.018 [pool-1-thread-153] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - 152
19:59:45.018 [pool-1-thread-154] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - 153
19:59:45.018 [pool-1-thread-155] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - 154
19:59:45.018 [pool-1-thread-157] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - 156
19:59:45.018 [pool-1-thread-158] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - 157
19:59:45.018 [pool-1-thread-159] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - 158
19:59:45.135 [main] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - finish!!

Process finished with exit code 0

尽管200个线程执行顺序是交叉的,但是最终输出结果的最后一行是finish!!,与期望的一致;

2.1 代码证明CountDownLatch能它允许一个或多个线程等待直到在其他线程中一组操作执行完成。

通过注释掉countDownLatch.await();语句,那么finsh!!日志内容,将会出现任何位置,而不会绝对仅仅出现在末尾。示例代码如下:

@Slf4j
public class CountDownLatchExample1 {

    private static final int threadCount = 200;

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final CountDownLatch countDownLatch = new CountDownLatch(threadCount);

        for (int i=0; i<threadCount; i++){
            final int threadNum = i;
            executorService.execute(()->{
                try {
                    test(threadNum);
                }catch (Exception e){
                    log.error("exception",e);
                }finally {
                    countDownLatch.countDown();
                }
            });
        }
        //注释掉下面一行代码
//        countDownLatch.await();
        log.info("finish!!");
        executorService.shutdown();
    }

    private static void test(int threadNum) throws InterruptedException {

        Thread.sleep(100);
        log.info("{}",threadNum);
        Thread.sleep(100);
    }
}

上述代码输出结果如下:

20:24:57.930 [main] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - finish!!
20:24:58.046 [pool-1-thread-9] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - 8
20:24:58.045 [pool-1-thread-5] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - 4
20:24:58.045 [pool-1-thread-4] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - 3
20:24:58.045 [pool-1-thread-6] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - 5

这是因为,输出finish!!内容的主线程,与线程池中的200个线程,没有任何执行顺序的约束。

2.2 代码证明CountDownLatch中计数器的存在

CountDownLatch countDownLatch= new CountDownLatch(N)中N的值有多大,那么countDownLatch.countDown()方法就必须执行多少次,否则无法执行countDownLatch.await();

将上述代码进行稍微修改,让for中的少执行一次``方法,代码如下:

@Slf4j
public class CountDownLatchExample1 {

    private static final int threadCount = 200;

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final CountDownLatch countDownLatch = new CountDownLatch(threadCount);
		//此处循环将199次,因此 countDownLatch.countDown()也只会执行了199次
        for (int i=0; i<threadCount-1; i++){
            final int threadNum = i;
            executorService.execute(()->{
                try {
                    test(threadNum);
                }catch (Exception e){
                    log.error("exception",e);
                }finally {
                    countDownLatch.countDown();
                }
            });
        }
        countDownLatch.await();
        log.info("finish!!");
        executorService.shutdown();
    }

    private static void test(int threadNum) throws InterruptedException {

        Thread.sleep(100);
        log.info("{}",threadNum);
        Thread.sleep(100);
    }
}

执行结果如下:

...
20:07:56.069 [pool-1-thread-72] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - 71
20:07:56.069 [pool-1-thread-73] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - 72
20:07:56.069 [pool-1-thread-75] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - 74
20:07:56.069 [pool-1-thread-78] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - 77
20:07:56.069 [pool-1-thread-79] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample1 - 78

上述代码运行之后,将会一直处于阻塞状态,且并没有输出“finish!!”日志内容。这是因为countDownLatch中的计数器state,一直无法减小到0,因此无法执行countDownLatch.await();后面的程序,

3. CountDownLatch的特殊使用

情景:如果线程池中有200个不同的线程同时在执行,需要在所有线程执行完后输出finish!!日志,但是如果有的线程等待时间超过10毫秒,那么就跳过该线程,直接输出finish!!日志。
针对上面的情景,可以将上述代码修改:

@Slf4j
public class CountDownLatchExample2 {

    private static final int threadCount = 200;

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final CountDownLatch countDownLatch = new CountDownLatch(threadCount);

        for (int i=0; i<threadCount; i++){
            final int threadNum = i;
            executorService.execute(()->{
                try {
                    test(threadNum);
                }catch (Exception e){
                    log.error("exception",e);
                }finally {
                    countDownLatch.countDown();
                }
            });
        }
        //此处通过设置countDownLatch.await()内的参数,达到要求;
        countDownLatch.await(10, TimeUnit.MILLISECONDS);
        log.info("finish!!");
        executorService.shutdown();
    }

    private static void test(int threadNum) throws InterruptedException {
        Thread.sleep(100);
        log.info("{}",threadNum);
    }
}

可以通过设置countDownLatch.await()内的参数,满足要求,其中第一个参数为需要等待的时间,第二个参数为时间单位。由于代码中每个线程执行时间都超过了100毫秒,所以最先输出finish!!日志,结果如下:

20:47:17.659 [main] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample2 - finish!!
20:47:17.723 [pool-1-thread-1] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample2 - 0
20:47:17.727 [pool-1-thread-2] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample2 - 1
20:47:17.730 [pool-1-thread-4] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample2 - 3
20:47:17.730 [pool-1-thread-3] INFO com.mmall.concurrency.example.aqs.CountDownLatchExample2 - 2
...

Java并发编程学习系列

如有帮助,烦请点赞收藏一下啦 (◕ᴗ◕✿)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值