项目中使用多线程

项目中使用多线程

线程池

1、线程池配置

项目中如果要使用线程池,必须先进行配置,如果使用spring 默认的线程池配置可能会导致OOM异常

  • 注意要在spring启动项 上加上@EnableAsync 开启异步支持
  • 线程池要配置@Configuration注解交给spring管理
  • 配置线程池主要参数
    • corePoolSize 核心线程
    • maximumPoolSize 最大线程
    • keepAliveTime 空闲线程存活时间
    • unit 空闲线程存活时间单位
    • workQueue 工作队列(4种)
      • ArrayBlockingQueue 新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。
      • LinkedBlockingQuene 当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而基本不会去创建新线程直到maxPoolSize
      • SynchronousQuene 新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。
      • PriorityBlockingQueue 具有优先级的无界阻塞队列,优先级通过参数Comparator实现。
    • threadFactory 线程工厂
    • handler 拒绝策略(4种)
      • CallerRunsPolicy 该策略下,在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务。
      • AbortPolicy 该策略下,直接丢弃任务,并抛出RejectedExecutionException异常。
      • DiscardPolicy 该策略下,直接丢弃任务,什么都不做。
      • DiscardOldestPolicy 该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列

线程池配置:

@Configuration
@EnableAsync
public class ExecutorTaskPoolConfig {/*** 核心线程数* 默认的核心线程数为1**/private static final int CORE_POOL_SIZE = 5;/*** 最大线程数* 默认的最大线程数是Integer.MAX_VALUE 即2<sup>31</sup>-1*/private static final int MAX_POOL_SIZE = 20;/*** 缓冲队列数* 默认的缓冲队列数是Integer.MAX_VALUE 即2<sup>31</sup>-1*/private static final int QUEUE_CAPACITY = 100;/*** 允许线程空闲时间* 默认的线程空闲时间为60秒*/private static final int KEEP_ALIVE_SECONDS = 60;/*** 线程池前缀名*/private static final String THREAD_NAME_PREFIX = "Lamp_Service_Async_";/*** allowCoreThreadTimeOut为true则线程池数量最后销毁到0个* allowCoreThreadTimeOut为false* 销毁机制:超过核心线程数时,而且(超过最大值或者timeout过),就会销毁。* 默认是false*/private boolean allowCoreThreadTimeOut = true;@Bean("taskExecutor")public ThreadPoolTaskExecutor taskExecutor() {ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();taskExecutor.setCorePoolSize(CORE_POOL_SIZE);taskExecutor.setMaxPoolSize(MAX_POOL_SIZE);taskExecutor.setQueueCapacity(QUEUE_CAPACITY);taskExecutor.setKeepAliveSeconds(KEEP_ALIVE_SECONDS);taskExecutor.setThreadNamePrefix(THREAD_NAME_PREFIX);taskExecutor.setAllowCoreThreadTimeOut(allowCoreThreadTimeOut);taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());//线程池初始化taskExecutor.initialize();return taskExecutor;}
}

2、实战使用

在spring 项目中 如果需要使用多线程来完成逻辑,则需要优先考虑线程安全问题,如果使用同一个对象 或者使用静态公有属性则极有可能会造成线程安全问题,所以使用中,多考虑 ,多测试模拟后再去使用

以下作为参考:

如果要使用线程池,就要在方法上加上@Async 注解,

需要标识是个异步方法

@EnableAsync来开启异步的支持

@Async来对某个方法进行异步执行

异步方法:

@Async("taskExecutor")
public void historyAllProcessing(LampChartDTO lampChartDTO, Map<Integer, BaseExportDTO> map1, Map<Integer, BaseExportDTO> map2, Map<Integer, BaseExportDTO> map3, Map<Integer, BaseExportDTO> map4,String lampId,String lampNo) {long start = System.currentTimeMillis();//1.查灯控CompletableFuture<Void> lightControlFuture = CompletableFuture.runAsync(() -> {BaseExportDTO result = new BaseExportDTO();//这里为了线程安全,new新对象,将对象重新赋值LampChartDTO l1 = new LampChartDTO();l1.setType(0);l1.setId(lampChartDTO.getId());l1.setEDate(lampChartDTO.getEDate());l1.setSDate(lampChartDTO.getSDate());result = getLampHistoryDatQueryAllData(result,l1,lampId,lampNo);map1.put(l1.getId(),result);});//2.查充放CompletableFuture<Void> chargeDischargeFuture = CompletableFuture.runAsync(() -> {BaseExportDTO result = new BaseExportDTO();LampChartDTO l2 = new LampChartDTO();l2.setType(1);l2.setId(lampChartDTO.getId());l2.setEDate(lampChartDTO.getEDate());l2.setSDate(lampChartDTO.getSDate());result = getLampHistoryDatQueryAllData(result,l2,lampId,lampNo);map2.put(l2.getId(),result);});//3.查蓄电CompletableFuture<Void> storageBatteryFuture = CompletableFuture.runAsync(() -> {BaseExportDTO result = new BaseExportDTO();LampChartDTO l3 = new LampChartDTO();l3.setType(2);l3.setId(lampChartDTO.getId());l3.setEDate(lampChartDTO.getEDate());l3.setSDate(lampChartDTO.getSDate());result = getLampHistoryDatQueryAllData(result,l3,lampId,lampNo);map3.put(l3.getId(),result);});//4.查太阳能板CompletableFuture<Void> solarEnergyFuture = CompletableFuture.runAsync(() -> {BaseExportDTO result = new BaseExportDTO();LampChartDTO l4 = new LampChartDTO();l4.setType(3);l4.setId(lampChartDTO.getId());l4.setEDate(lampChartDTO.getEDate());l4.setSDate(lampChartDTO.getSDate());result = getLampHistoryDatQueryAllData(result,l4,lampId,lampNo);map4.put(l4.getId(),result);});//5.等待所有线程执行完毕CompletableFuture.allOf(lightControlFuture, chargeDischargeFuture, storageBatteryFuture, solarEnergyFuture).join();long end = System.currentTimeMillis();log.info("Lamp系统 ----> 线程:" + Thread.currentThread().getName() + " , " + lampNo + "的设备 , 数据查询,耗时 :" + (end - start) + "ms");}

可以看到以上参考使用了CompletableFuture 这个目前了解到如果使用了CompletableFuture 那么在后面使用如下代码

CompletableFuture.allOf(lightControlFuture, chargeDischargeFuture, storageBatteryFuture, solarEnergyFuture).join();

这个是表示所有任务都完成的情况下才会结束,但凡有一个线程没有结束,这里就会一直等。这个使用可以保证线程中如果需要批量处理数据但又想知道结果的方案上,CompletableFuture 还是非常好用的,具体参考:CompletableFuture

CountDownLatch

1、CountDownLatch介绍

CountDownLatch 是 Java 中的一个同步工具类,它允许一个或多个线程等待其他线程完成操作。CountDownLatch 的主要思想是,一个线程等待其他线程完成一组操作,它在倒计时计数器的基础上工作,计数器的初始值是一个正整数,每当一个线程完成一项操作,计数器的值减一。当计数器的值变为零时,等待的线程被释放,可以继续执行。

2、CountDownLatch源码解析

public class CountDownLatch {// 构造方法public CountDownLatch(int count) {if (count < 0) throw new IllegalArgumentException("count < 0");this.sync = new Sync(count);}// 用于阻塞当前线程,直到计数器的值变为零public void await() throws InterruptedException {// 在 Sync 类中实现,用于尝试获取共享许可,如果计数器为零,则直接返回,否则会进入等待队列等待。sync.acquireSharedInterruptibly(1);}// 用于等待一段时间,超时后返回public boolean await(long timeout, TimeUnit unit)throws InterruptedException {// 用于尝试在规定的时间内获取共享许可,如果在超时前获取到许可则返回 true,否则返回 false。return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));}// 用于将计数器的值减一,表示有一个线程完成了一项操作public void countDown() {// 在 Sync 类中实现,用于释放共享许可sync.releaseShared(1);}// 用于获取当前计数器的值public long getCount() {// 返回当前的计数值return sync.getCount();}
}

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 zerofor (;;) {int c = getState();if (c == 0)return false;int nextc = c-1;if (compareAndSetState(c, nextc))return nextc == 0;}}
}

3、CountDownLatch应用场景

  • 多线程任务协同: CountDownLatch 可以用于多个线程协同完成某项任务。一个主线程等待其他多个线程完成任务,每个子线程完成一部分工作后调用 countDown() 方法减少计数值,主线程通过 await() 方法等待计数值变为零。
  • 并行任务的性能测试: 可以用 CountDownLatch 来测量并行任务的性能,主线程可以等待多个并行任务同时开始执行。
  • 等待多个服务初始化完成: 在系统启动时,有时需要等待多个服务初始化完成后再继续执行。每个服务初始化完成后调用 countDown()。
  • 分布式系统中的协同: 在分布式系统中,CountDownLatch 可以用于等待多个节点的某个事件的发生,以协同分布式系统的操作。

4、示例

普通示例

public class CountDownLatchTest {public static void main(String[] args) {final CountDownLatch latch = new CountDownLatch(2);System.out.println("主线程开始执行…… ……");//第一个子线程执行ThreadUtil.execute(() -> {try {Thread.sleep(3000);System.out.println("子线程:"+Thread.currentThread().getName()+"执行");} catch (InterruptedException e) {throw new RuntimeException(e);}latch.countDown();});//第二个子线程执行ThreadUtil.execute(() -> {try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("子线程:"+Thread.currentThread().getName()+"执行");latch.countDown();});System.out.println("等待两个线程执行完毕…… ……");try {latch.await();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("两个子线程都执行完毕,继续执行主线程");}
}

结果集

主线程开始执行…… ……
等待两个线程执行完毕…… ……
子线程:pool-1-thread-1执行
子线程:pool-2-thread-1执行
两个子线程都执行完毕,继续执行主线程

模拟拼团活动

public class CountDownLatchDemo {public static void main(String[] args) {pt();}private static void pt() {// 模拟拼团活动final CountDownLatch countDownLatch = new CountDownLatch(10);List<String> ids = new ArrayList<>();// 目前有10个人进行拼团for (int i = 0; i < 30; i++) {new Thread(() -> {if (countDownLatch.getCount() > 0) {synchronized (ids) {if (countDownLatch.getCount() > 0) {ids.add(Thread.currentThread().getName());System.out.println(Thread.currentThread().getName() + "拼团成功!!!");countDownLatch.countDown();}}}System.out.println(Thread.currentThread().getName() + "请求拼团!!!商品剩余" + countDownLatch.getCount());}, String.valueOf(i + 1)).start();}new Thread(() -> {try {countDownLatch.await();System.out.println("拼团结束============================================================");System.out.println("拼团人员id"+ids);} catch (InterruptedException e) {e.printStackTrace();}}, "拼团").start();}
}

结果集

1拼团成功!!!
1请求拼团!!!商品剩余9
4拼团成功!!!
4请求拼团!!!商品剩余8
8拼团成功!!!
8请求拼团!!!商品剩余7
6拼团成功!!!
14拼团成功!!!
6请求拼团!!!商品剩余6
5拼团成功!!!
5请求拼团!!!商品剩余4
14请求拼团!!!商品剩余5
7拼团成功!!!
7请求拼团!!!商品剩余3
3拼团成功!!!
3请求拼团!!!商品剩余2
2拼团成功!!!
22拼团成功!!!
2请求拼团!!!商品剩余1
19请求拼团!!!商品剩余0
24请求拼团!!!商品剩余0
13请求拼团!!!商品剩余0
25请求拼团!!!商品剩余0
9请求拼团!!!商品剩余0
20请求拼团!!!商品剩余0
28请求拼团!!!商品剩余0
22请求拼团!!!商品剩余0
30请求拼团!!!商品剩余0
21请求拼团!!!商品剩余0
27请求拼团!!!商品剩余0
17请求拼团!!!商品剩余0
11请求拼团!!!商品剩余0
10请求拼团!!!商品剩余0
12请求拼团!!!商品剩余0
15请求拼团!!!商品剩余0
23请求拼团!!!商品剩余0
16请求拼团!!!商品剩余0
18请求拼团!!!商品剩余0
拼团结束============================================================
26请求拼团!!!商品剩余0
29请求拼团!!!商品剩余0
拼团人员id[1, 4, 8, 6, 14, 5, 7, 3, 2, 22]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/50364.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringMVC笔记详解

SpringMVC 1、SpringMVC简介 1.1、什么是MVC MVC是一种软件架构的思想&#xff0c;将软件按照模型、视图、控制器来划分 M&#xff1a;Model&#xff0c;模型层&#xff0c;指工程中的JavaBean&#xff0c;作用是处理数据 JavaBean分为两类&#xff1a; 一类称为实体类Be…

[JavaScript] 动态获取方法参数名

JavaScript&#xff08;简称“JS”&#xff09;是一种具有函数优先的轻量级&#xff0c;解释型或即时编译型的编程语言。虽然它是作为开发Web页面的脚本语言而出名&#xff0c;但是它也被用到了很多非浏览器环境中&#xff0c;JavaScript基于原型编程、多范式的动态脚本语言&am…

Optional类的使用 java8(附代码)

&#x1f370; 个人主页:_小白不加班__ &#x1f35e;文章有不合理的地方请各位大佬指正。 &#x1f349;文章不定期持续更新&#xff0c;如果我的文章对你有帮助➡️ 关注&#x1f64f;&#x1f3fb; 点赞&#x1f44d; 收藏⭐️ 文章目录 一、什么是Optional&#xff1f;二、…

科研绘图系列:R语言和弦图 (Chord diagram)

介绍 和弦图(Chord Diagram)是一种用于展示多个实体之间相互关系的数据可视化方法。它通常用于表示网络或系统中不同节点(实体)之间的连接强度或流量。和弦图由一个圆形布局组成,每个节点在圆周上占据一个扇形区域,节点之间的连接通过圆内的线条(和弦)来表示。 特点:…

linux环境下重新编译opencv的安卓动态链接库opencv_java4.so文件

一、安装java 1.8 #安装依赖 sudo apt-get install python-software-properties sudo add-apt-repository ppa:webupd8team/java sudo apt-get update sudo add-apt-repository ppa:ts.sch.gr/ppa sudo apt-get update sudo apt-get install oracle-java8-installer 二…

VB中的函数和子程序(Sub)的区别和用法

在VB&#xff08;Visual Basic&#xff09;及其派生语言如VBA&#xff08;Visual Basic for Applications&#xff09;中&#xff0c;函数&#xff08;Function&#xff09;和子程序&#xff08;Sub&#xff09;是两种不同类型的代码块&#xff0c;它们在用途、返回值以及调用方…

前端必知必会-html的id属性和Iframe

文章目录 HTML id 属性类和 ID 之间的区别带有 ID 和链接的 HTML 书签在 JavaScript 中使用 id 属性HTML IframeIframe - 设置高度和宽度Iframe - 删除边框Iframe - 链接的目标总结 HTML id 属性 HTML id 属性用于为 HTML 元素指定唯一 id。id 属性的值在 HTML 文档中必须是唯…

AI推介-大语言模型LLMs论文速览(arXiv方向):2024.07.15-2024.07.20

文章目录&#xff5e; 1.SegPoint: Segment Any Point Cloud via Large Language Model2.LLMs as Function Approximators: Terminology, Taxonomy, and Questions for Evaluation3.Scaling Laws with Vocabulary: Larger Models Deserve Larger Vocabularies4.Large Language …

数据结构第七讲:栈和队列OJ题

数据结构第七讲&#xff1a;栈和队列OJ题 1.有效的括号2.用队列实现栈3.用栈实现队列4.设计循环队列 1.有效的括号 链接: OJ题目链接 typedef char StackDataType;typedef struct Stack {StackDataType* arr;//使用一个指针来指向开辟的数组int capacity;//保存数组的空间大…

微信小程序注册及认证

进行了两个小程序的注册及认证&#xff0c;记录一下 首先&#xff0c;注册小程序通常都是给企业注册&#xff0c;有些资料最好事先准备&#xff0c;考虑到认证、备案的审核&#xff0c;以及对公打款需要走公司内的审批等&#xff0c;整个过程可能需要一到两周的时间。 准备工…

【ffmpeg命令入门】获取音视频信息

文章目录 前言使用ffmpeg获取简单的音视频信息输入文件信息文件元数据视频流信息音频流信息 使用ffprobe获取更详细的音视频信息输入文件信息文件元数据视频流信息音频流信息 总结 前言 在处理多媒体文件时&#xff0c;了解文件的详细信息对于调试和优化处理过程至关重要。FFm…

后端面试题日常练-day11 【Java基础】

题目 希望这些选择题能够帮助您进行后端面试的准备&#xff0c;答案在文末 Java中的String和StringBuffer/StringBuilder有何区别&#xff1f; a) String是不可变的&#xff0c;StringBuffer/StringBuilder是可变的 b) String是线程安全的&#xff0c;StringBuffer/StringBuil…

springboot爱宠屋宠物商店管理系统-计算机毕业设计源码52726

目录 摘要 1 绪论 1.1 选题背景与意义 1.2国内外研究现状 1.3论文结构与章节安排 2系统分析 2.1 可行性分析 2.2 系统流程分析 2.2.1系统开发流程 2.2.2 用户登录流程 2.2.3 系统操作流程 2.2.4 添加信息流程 2.2.5 修改信息流程 2.2.6 删除信息流程 2.3 系统功能…

【机器学习】正规方程的简单介绍以及如何使用Scikit-Learn实现基于正规方程的闭式解线性回归

引言 Scikit-learn 是一个开源的机器学习库&#xff0c;它支持 Python 编程语言。它提供了多种机器学习算法的实现&#xff0c;并用于数据挖掘和数据分析 文章目录 引言一、正规方程的定义二、正规方程的原理三、使用 Scikit-Learn 实现基于正规方程的闭式解线性回归3.1 工具3.…

实验15.多线程调度

简介 实验.多线程调度 内核线程 1.在时钟中断函数中处理中&#xff0c;减少当前线程pcb的tick&#xff0c;tick为0则启动调度2.调度&#xff0c;把当前线程pcb放入就绪对立队尾&#xff0c;把就绪线程队首拿出来执行主要代码 引导 省略内核 list.h #ifndef __LIB_KERNEL_…

Nginx缓存配置实现CDN加速

Nginx缓存配置实现CDN加速 1. 前言2. 配置介绍2.1 proxy_cache_path2.2 proxy_cache2.3 proxy_cache_key2.4 proxy_cache_lock2.5 proxy_cache_lock_timeout2.6 proxy_cache_lock_age2.7 proxy_cache_min_uses2.8 proxy_cache_purge2.9 proxy_cache_valid 3. 强制分片4. 配置示…

【2024最新】 服务器安装Ubuntu20.04 (安装教程、常用命令、故障排查)持续更新中.....

安装教程&#xff08;系统、NVIDIA驱动、CUDA、CUDNN、Pytorch、Timeshift、ToDesk、花生壳&#xff09; 制作U盘启动盘&#xff0c;并安装系统 在MSDN i tell you下载Ubuntu20.04 Desktop 版本&#xff0c;并使用Rufus制作UEFI启动盘&#xff0c;参考UEFI安装Ubuntu使用GPTU…

mysql 的MHA

mysql 的MHA 什么是MHA 高可用模式下的故障切换&#xff0c;基于主从复制。 单点故障和主从复制不能切换的问题。 至少需要3台。 故障切换过程0-30秒。 vip地址&#xff0c;根据vip地址所在的主机&#xff0c;确定主备。 主 vip 备 vip 主和备不是优先确定的&#xff…

文件内容查找-Windows Linux

findstr /s /i /m /c:^huggingface.co^ *.js C盘的js文件内容包含huggingface.co 的文件名 grep "keyword" -r filename

InternLM Linux 基础知识

完成SSH连接与端口映射并运行hello_world.py 创建并运行test.sh文件 使用 VSCODE 远程连接开发机并创建一个conda环境