SpringBoot 多任务并行+线程池处理的实现
作者:小柒 发布时间:2023-04-02 01:16:25
前言
前几篇文章着重介绍了后端服务数据库和多线程并行处理优化,并示例了改造前后的伪代码逻辑。当然了,优化是无止境的,前人栽树后人乘凉。作为我们开发者来说,既然站在了巨人的肩膀上,就要写出更加优化的程序。
SpringBoot开发案例之JdbcTemplate批量操作
SpringBoot开发案例之CountDownLatch多任务并行处理
改造
理论上讲,线程越多程序可能更快,但是在实际使用中我们需要考虑到线程本身的创建以及销毁的资源消耗,以及保护操作系统本身的目的。我们通常需要将线程限制在一定的范围之类,线程池就起到了这样的作用。
程序逻辑
多任务并行+线程池处理.png
一张图能解决的问题,就应该尽可能的少BB,当然底层原理性的东西还是需要大家去记忆并理解的。
Java 线程池
Java通过Executors提供四种线程池,分别为:
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
优点
重用存在的线程,减少对象创建、消亡的开销,性能佳。
可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
提供定时执行、定期执行、单线程、并发数控制等功能。
代码实现
方式一(CountDownLatch)
/**
* 多任务并行+线程池统计
* 创建时间 2018年4月17日
*/
public class StatsDemo {
final static SimpleDateFormat sdf = new SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss");
final static String startTime = sdf.format(new Date());
/**
* IO密集型任务 = 一般为2*CPU核心数(常出现于线程中:数据库数据交互、文件上传下载、网络数据传输等等)
* CPU密集型任务 = 一般为CPU核心数+1(常出现于线程中:复杂算法)
* 混合型任务 = 视机器配置和复杂度自测而定
*/
private static int corePoolSize = Runtime.getRuntime().availableProcessors();
/**
* public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,
* TimeUnit unit,BlockingQueue<Runnable> workQueue)
* corePoolSize用于指定核心线程数量
* maximumPoolSize指定最大线程数
* keepAliveTime和TimeUnit指定线程空闲后的最大存活时间
* workQueue则是线程池的缓冲队列,还未执行的线程会在队列中等待
* 监控队列长度,确保队列有界
* 不当的线程池大小会使得处理速度变慢,稳定性下降,并且导致内存泄露。如果配置的线程过少,则队列会持续变大,消耗过多内存。
* 而过多的线程又会 由于频繁的上下文切换导致整个系统的速度变缓——殊途而同归。队列的长度至关重要,它必须得是有界的,这样如果线程池不堪重负了它可以暂时拒绝掉新的请求。
* ExecutorService 默认的实现是一个 * 的 LinkedBlockingQueue。
*/
private static ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, corePoolSize+1, 10l, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(1000));
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(5);
//使用execute方法
executor.execute(new Stats("任务A", 1000, latch));
executor.execute(new Stats("任务B", 1000, latch));
executor.execute(new Stats("任务C", 1000, latch));
executor.execute(new Stats("任务D", 1000, latch));
executor.execute(new Stats("任务E", 1000, latch));
latch.await();// 等待所有人任务结束
System.out.println("所有的统计任务执行完成:" + sdf.format(new Date()));
}
static class Stats implements Runnable {
String statsName;
int runTime;
CountDownLatch latch;
public Stats(String statsName, int runTime, CountDownLatch latch) {
this.statsName = statsName;
this.runTime = runTime;
this.latch = latch;
}
public void run() {
try {
System.out.println(statsName+ " do stats begin at "+ startTime);
//模拟任务执行时间
Thread.sleep(runTime);
System.out.println(statsName + " do stats complete at "+ sdf.format(new Date()));
latch.countDown();//单次任务结束,计数器减一
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
方式二(Future)
/**
* 多任务并行+线程池统计
* 创建时间 2018年4月17日
*/
public class StatsDemo {
final static SimpleDateFormat sdf = new SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss");
final static String startTime = sdf.format(new Date());
/**
* IO密集型任务 = 一般为2*CPU核心数(常出现于线程中:数据库数据交互、文件上传下载、网络数据传输等等)
* CPU密集型任务 = 一般为CPU核心数+1(常出现于线程中:复杂算法)
* 混合型任务 = 视机器配置和复杂度自测而定
*/
private static int corePoolSize = Runtime.getRuntime().availableProcessors();
/**
* public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,
* TimeUnit unit,BlockingQueue<Runnable> workQueue)
* corePoolSize用于指定核心线程数量
* maximumPoolSize指定最大线程数
* keepAliveTime和TimeUnit指定线程空闲后的最大存活时间
* workQueue则是线程池的缓冲队列,还未执行的线程会在队列中等待
* 监控队列长度,确保队列有界
* 不当的线程池大小会使得处理速度变慢,稳定性下降,并且导致内存泄露。如果配置的线程过少,则队列会持续变大,消耗过多内存。
* 而过多的线程又会 由于频繁的上下文切换导致整个系统的速度变缓——殊途而同归。队列的长度至关重要,它必须得是有界的,这样如果线程池不堪重负了它可以暂时拒绝掉新的请求。
* ExecutorService 默认的实现是一个 * 的 LinkedBlockingQueue。
*/
private static ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, corePoolSize+1, 10l, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(1000));
public static void main(String[] args) throws InterruptedException {
List<Future<String>> resultList = new ArrayList<Future<String>>();
//使用submit提交异步任务,并且获取返回值为future
resultList.add(executor.submit(new Stats("任务A", 1000)));
resultList.add(executor.submit(new Stats("任务B", 1000)));
resultList.add(executor.submit(new Stats("任务C", 1000)));
resultList.add(executor.submit(new Stats("任务D", 1000)));
resultList.add(executor.submit(new Stats("任务E", 1000)));
//遍历任务的结果
for (Future<String> fs : resultList) {
try {
System.out.println(fs.get());//打印各个线任务执行的结果,调用future.get() 阻塞主线程,获取异步任务的返回结果
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} finally {
//启动一次顺序关闭,执行以前提交的任务,但不接受新任务。如果已经关闭,则调用没有其他作用。
executor.shutdown();
}
}
System.out.println("所有的统计任务执行完成:" + sdf.format(new Date()));
}
static class Stats implements Callable<String> {
String statsName;
int runTime;
public Stats(String statsName, int runTime) {
this.statsName = statsName;
this.runTime = runTime;
}
public String call() {
try {
System.out.println(statsName+ " do stats begin at "+ startTime);
//模拟任务执行时间
Thread.sleep(runTime);
System.out.println(statsName + " do stats complete at "+ sdf.format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
return call();
}
}
}
执行时间
以上代码,均是伪代码,下面是2000+个学生的真实测试记录。
2018-04-17 17:42:29.284 INFO 测试记录81e51ab031eb4ada92743ddf66528d82-单线程顺序执行,花费时间:3797
2018-04-17 17:42:31.452 INFO 测试记录81e51ab031eb4ada92743ddf66528d82-多线程并行任务,花费时间:2167
2018-04-17 17:42:33.170 INFO 测试记录81e51ab031eb4ada92743ddf66528d82-多线程并行任务+线程池,花费时间:1717
来源:https://blog.52itstyle.com/archives/2705/
猜你喜欢
- 我们在使用SpringBoot进行测试的时候一般是需要加两个注解:@SpringBootTest目的是加载ApplicationContex
- 文件的上传与下载(一)在实现文件上传和下载之前我们需要做一些准备工作,在Apache官网去下载文件上传下载的两个组件,下载链接这里给出:co
- 整理文档,搜刮出一个Spring 实现excel及pdf导出表格的代码,稍微整理精简一下做下分享。excel 导出:package ligh
- 今天看到已经更新了devblogs,新增的C# 11的!!(用于检查null的语法)经过非常长的讨论,最后取消了。然后我又想起来null检查
- 在hibernate5中,有了一些新的变动: 新引导 APISpatial/GIS 支持Java 8 支持扩展 AUTO
- 本文实例为大家分享了Unity3D网格功能生成球体网格模型的具体代码,供大家参考,具体内容如下前面已经讲过怎样使用mesh生成一个自己的网格
- 原生Toast样式自定义Toast样式创建样式所谓自定义一个Toast就是建立一个布局文件,然后使用一个view容器承载,然后显示出来。To
- 本文实例讲述了Java正则验证IP的方法。分享给大家供大家参考,具体如下:网上用正则验证IP的表达式有很多,一搜一大堆,可以自己写,但很麻烦
- Android中Uri和Path之间的转换原因调用系统拍照应用,拍照后要保存图片,那么我们需要指定一个存储图片路径的Uri。这就涉及到如何将
- 一、基本概念JavaWeb里面的listener是通过观察者设计模式进行实现的。对于观察者模式,这里不做过多介绍,大概讲一下什么意思。观察者
- eclipse汉化包我们会经常用到,因为它可以帮助英语基础差的用户更快的掌握这个软件,非常方便,但是我看了下整个互联网上并没有对这个汉化包进
- 在很多系统开发中,我们希望在指定的方法调用之前或者之后能打印出该方法的调用时间以及方法的出参和入参,就可以使用spring的AOP,还可以结
- 之前一篇文章中我们讲了基于Mysql8的读写分离(文末有链接),这次来说说分库分表的实现过程。概念解析垂直分片按照业务拆分的方式称为垂直分片
- 抛砖今天使用monio做S3存储时,添加云服务器初始化时一直在构建客户端抛出异常。MinioClient.builder() //NoCla
- 一、安装Maven下载地址:https://maven.apache.org/download.cgi把下载的安装包解压tar -xvf a
- 前言上文讲的MyBatis部署运行且根据官网运行了一个demo:一步到位部署运行MyBatis3源码<保姆级>jdbc再贴一个J
- AOP拦截Controller获取@PathVariable注解传入参数前言:最近项目中需要对controller传入的应用标识(appMa
- swing自带的窗体是不能够满足我们的应用需求的,所以需要制作任意图片和形状的JFrame框体,比如下图:并且可以设置窗体背景图片的透明度下
- 要求:如下图,使用线程操作 1、实时显示当前时间 2、输入加数和被加数,自动出现结果 分析:两个问题解决的方式一致,使用子线程进
- 最近看到在Linux上折腾jmeter的人越来越多,不过即使在windows上,jmeter的脚本我还是建议用命令行来执行(降低GUI模式带