Java并发编程之CountDownLatch


原文链接:https://blog.csdn.net/qq877728715/article/details/121857013

1、并发请求介绍

java中模拟并发请求,只要多开几个线程,发起请求就好了。但是,这种请求,一般会存在启动的先后顺序了,算不得真正的同时并发!怎么样才能做到真正的同时并发呢?java中提供了闭锁 CountDownLatch、信号量Semaphore、同步屏障CyclicBarrier, 刚好就用来做这种事就最合适了

本文主要记录CountDownLatch

1. 开启n个线程,加一个闭锁,开启所有线程;
2. 待所有线程都准备好后,按下开启按钮,就可以真正的发起并发请求了。

2、CountDownLatch执行逻辑

CountDownLatch俗称:(同步计数器/闭锁),可以使一个线程等待其他线程全部执行完毕后再执行。 类似join()的效果。

  • 场景:主要用来解决一个线程等待 N 个线程的场景。通常用来汇总各个线程执行后的结果
  • CountDownLatch内部通过一个计数器来控制等待线程数,该计数器的操作是原子操作,即同时只能有一个线程去更新该计数器。
    • 调用await()方法的线程会一直处于阻塞状态,直到其他线程调用countDown()使当前计数器的值变为 0,每次调用countDown()方法计数器的值减1。
    • 当计数器值减至0时,所有因调用await()方法而处于等待状态的线程就会被唤醒然后继续往下执行。
    • 这种现象只会出现一次,因为计数器不能被重置。

并发请求流程示意图:

image-20220730163123424

设置了一道门,以保证所有线程可以同时生效。但是,这里的同时启动,也只是语言层面的东西,也并非绝对的同时并发。具体的调用还要依赖于CPU个数,线程数及操作系统的线程调度功能等,不过咱们也无需纠结于这些了,重点在于理解原理!

与 CountDownLatch 有类似功能的,还有工具栅栏 CyclicBarrier, 也是提供一个等待所有线程到达某一点后,再一起开始某个动作,效果一致,不过栅栏的目的确实比较纯粹,就是等待所有线程到达,而前面说的闭锁 CountDownLatch 虽然实现的也是所有线程到达后再开始,但是他的触发点其实是 最后那一个开关,所以侧重点是不一样的。

3、实例代码

示例代码:

1、定义了一个开始门,一个结束门,开始门的线程数是1,结束门的线程数是1500,

2、开始门用来阻塞所有的线程,所有线程创建后,在开始门前阻塞;当countDown()==0时,阻塞线程后续的逻辑开始执行

3、结束门用来统计所有线程的执行时间;当开始门开始时开始计时,每执行一个线程countDown,当countDown()==0时,结束门的阻塞失效,程序开始向下执行,线程执行完成,统计执行时间

package com.codesheep.terminaldemo.config;

import com.codesheep.terminaldemo.service.TaskClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @ClassName TerminalInfo
 * @description:
 * @author: codesheep
 * @Version 1.0.0
 * @createTime: 2022-07-29 13:17:48
 */
@Component
public class TerminalInfo {
    private static final Logger logger = LoggerFactory.getLogger(TerminalInfo.class);
    private static int TERMINAL_COUNT = 1500;
    private static int COUNT = 0;
    // 定义线程池
    ExecutorService executorService = Executors.newCachedThreadPool();
    //开始门
    final CountDownLatch startGate = new CountDownLatch(1);
    //结束门
    final CountDownLatch endGate = new CountDownLatch(TERMINAL_COUNT);
    
    public void process1(){
        for (int i = 0; i < TERMINAL_COUNT; i++) {
            executorService.execute(() -> {
                try {
                    // 使线程在此等待,当开始门打开时,一起涌入门中
                    startGate.await();
                    try {
                        // 执行业务逻辑:发起请求
                        process2();
                    } finally {
                        // 业务执行完毕:将结束门减1,减到0时,就可以开启结束门了
                        endGate.countDown();
                    }
                } catch (InterruptedException ie) {
                    ie.printStackTrace();
                }
            });
        }
        long startTime = System.nanoTime();
        logger.info(startTime + " [" + Thread.currentThread() + "] 所有线程都准备好了,准备并发运行...");

        // startGate为0,立马执行该startGate阻塞的线程(线程向后执行),因开始门只需一个开关,所以立马就开启开始门
        startGate.countDown();

        // 阻塞线程不让向下执行(直到endGate为0,此处是所有线程执行完毕):等结束门开启(endGate为0,所有的任务执行完毕,统计执行时间)
        try {
            endGate.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //返回并发执行耗时(纳秒)
        long endTime = System.nanoTime();
        logger.info(endTime + " [" + Thread.currentThread() + "] 所有的线程执行完成."+"   耗时:"+(endTime - startTime)+" ns");
    }

    private void process2() {
        TaskClient newTaskClient = new TaskClient("1","119.167.1.112", 8888);
        newTaskClient.connect();
        COUNT++;
        logger.info("٩(๑❛❛๑)۶ ===终端数=== " + COUNT +" === "+ LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
    }
}

文章作者: superzqbo
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 superzqbo !
评论
  目录