Java线程池的使用

书接前文Java线程池及其实现原理

常用线程池有:

CachedThreadPool

FixedThreadPool

SingleThreadExecutor

ScheduledThreadPool

SingleThreadScheduledExecutor

Executors

.newCachedThreadPool();

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
内部实现:new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,

TimeUnit.SECONDS,new SynchronousQueue());

Executors

.newFixedThreadPool(int);

创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
内部实现:new ThreadPoolExecutor(nThreads, nThreads,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue());

Executors

.newSingleThreadExecutor();

创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照顺序执行。
内部实现:new ThreadPoolExecutor(1,1,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue())

Executors

.newScheduledThreadPool(int)

创建一个定长线程池,支持定时及周期性任务执行。
内部实现:new ScheduledThreadPoolExecutor(corePoolSize)
Executors
.newSingleThreadScheduledExecutor()
创建一个单线程的任务调度池(定时任务/延时任务)

线程池使用场景

场景1:快速响应用户请求

场景2:快速处理批量任务

阿里巴巴Java开发手册中对线程池的使用规范

  1. 【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯
  2. 【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
    说明: 使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
  3. 【强制】线程池不允许使用 Executors 去创建,而是通过ThreadPoolExecutor的方式创建,这样的处理方式让写的Javer更加明确线程池的运行规则,规避资源耗尽的风险。
    说明: Executors 返回的线程池对象的弊端如下:
    1) FixedThreadPool 和 SingleThreadPool:
    允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
    2) CachedThreadPool 和 ScheduledThreadPool:
    允许的创建线程数量为 Integer.MAX_VALUE, 可能会创建大量的线程,从而导致 OOM。

综上所述,建议使用ThreadPoolExecutor executor = new ThreadPoolExecutor(...);

线程池关闭

如果线程池没有做好关闭,可能会导致oom,严重可能会导致服务挂掉。

手动线程池

手动线程池关闭分为:shutdown和shutdownNow。

executor.shutdown();executor.shutdownNow();

区别:

shutdown主要做两件事:
  • 把线程池状态置为 SHUTDOWN 状态
  • 中断空闲线程
shutdownNow主要做三件事:
  • 把线程池状态置为 STOP 状态
  • 中断工作线程
  • 把线程池中的任务都 drain 出来并返回

综上,shutdown方法会等待把线程池中的任务都执行完,而shutdownNow会直接中断当前工作线程。因为,shutdown方法只是把空闲的 woker 置为中断,不影响正在运行的woker,并且会继续把待执行的任务给处理完。shutdonwNow方法则是把所有的 woker 都置为中断,待执行的任务全部抽出并返回。

日常工作中更多是使用 shutdown()。

如果要优雅的关闭线程池,则可以使用 awaitTermination() 和 JVM 的钩子来实现。

Thread shutdownHook = new Thread(() -> {executor.shutdown();try {executor.awaitTermination(3, TimeUnit.MINUTES);} catch (InterruptedException e) {log.info("等待超时,直接关闭");}
});
Runtime.getRuntime().addShutdownHook(shutdownHook);

自动关闭线程池

核心线程数为 0 并指定线程存活时间

@Test
public void test(){// 重点关注 corePoolSize 和 keepAliveTime,其他参数不重要ThreadPoolExecutor executor = new ThreadPoolExecutor(0,5,15L,TimeUnit.SECONDS,new LinkedBlockingQueue<>(15));for (int i = 0; i < 20; i++) {executor.execute(() -> {// 简单地打印当前线程名称System.out.println(Thread.currentThread().getName());});}
}

缺点:如果将corePoolSize设置为0的话,新到来的任务会永远优先被放入任务队列,然后等待被处理,这显然会影响程序的执行效率。

通过 allowCoreThreadTimeOut 控制核心线程存活时间

@Test
public void test(){// 这里把corePoolSize设为5,keepAliveTime保持不变ThreadPoolExecutor executor = new ThreadPoolExecutor(5,5,15L,TimeUnit.SECONDS,new LinkedBlockingQueue<>(15));// 允许核心线程超时销毁executor.allowCoreThreadTimeOut(true);for (int i = 0; i < 20; i++) {executor.execute(() -> {System.out.println(Thread.currentThread().getName());});}
}

线程池中的线程设置为守护线程

@Test
public void test(){// 这里把corePoolSize设为5,keepAliveTime保持不变ThreadPoolExecutor executor = new ThreadPoolExecutor(5,5,15L,TimeUnit.SECONDS,new LinkedBlockingQueue<>(15),new ThreadFactory() {@Overridepublic Thread newThread(@NotNull Runnable r) {Thread thread = new Thread(r, r.hashCode()+"");//设置成守护线程thread.setDaemon(true);return thread;}});// 允许核心线程超时销毁executor.allowCoreThreadTimeOut(true);for (int i = 0; i < 20; i++) {executor.execute(() -> {System.out.println(Thread.currentThread().getName());});}
}

PS:守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。也就是说守护线程不依赖于终端,但是依赖于系统,与系统“同生共死”。

线程池大小设计

首先需要确定该应用的预估并发量,假设每秒钟需要处理 100 个请求。然后根据业务特点,可做如下设定:

  • 确定线程池大小:为了充分利用 CPU 和内存资源,可以将线程池大小设置为 CPU 核心数的两倍。假设 CPU 核心数为 8,那么线程池大小可设置为 16。
  • 确定任务队列容量:由于这里的任务是从数据库中读取数据并进行计算,因此任务执行时间相对较长,需要将任务队列容量设置得大一些。假设任务队列容量为 100。

也可通过Runtime.getRuntime().availableProcessors()获取当前CPU核心数。

局部使用

int size = Runtime.getRuntime().availableProcessors() * 2;
ThreadPoolExecutor executor = new ThreadPoolExecutor(size,size,100,TimeUnit.MILLISECONDS,new ArrayBlockingQueue<>(100),Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardOldestPolicy());
//修改报价单状态->已转为销售订单
executor.execute(() -> xx.changeQuoteStatus(accessToken, thirdId, userId));
Thread shutdownHook = new Thread(() -> {executor.shutdown();try {executor.awaitTermination(3, TimeUnit.MINUTES);} catch (InterruptedException e) {log.info("等待超时,直接关闭");}
});
Runtime.getRuntime().addShutdownHook(shutdownHook);

parallelStream并行流

parallelStream并行流的底层是ForkJoinPool,如果要设置并行流线程参数,需要

private int parallelThreads = Runtime.getRuntime().availableProcessors() * 2;
ForkJoinPool pool = new ForkJoinPool(parallelThreads);
ForkJoinTask task = pool.submit(() -> {Lists.partition(list, 100).parallelStream().forEach(...);
});
task.join();
pool.shutdown();

全局配置使用

配置

/*** @author lyonardo* @createTime 2019年04月11日 10:14:23* @Description*/
@Configuration
@EnableAsync
public class TaskExecutorConfig {@Bean(name = "taskExecutor")public ThreadPoolTaskExecutor taskExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();//线程池大小executor.setCorePoolSize(16);//最大线程池大小executor.setMaxPoolSize(16);//任务队列容量executor.setQueueCapacity(100);//线程前缀名executor.setThreadNamePrefix("taskExecutor-");// 任务拒绝策略executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());executor.initialize();return executor;}
}

使用

使用线程池时,可以通过注解 @Async 将方法标记为异步方法,让方法在另一个线程中执行:

@Test
@Async("taskExecutor")
public void testExecute(){log.info("213");
}

在代码中,使用了 @Async 注解将 testExecute() 方法标记为异步方法,并指定了线程池名称为 "taskExecutor",表示该方法将在 "taskExecutor" 线程池中执行。这样就可以避免在主线程中等待查询和计算的结果,提高了应用的响应速度。

如果需要接收异步操作的结果,可以使用Future做如下操作:

@Async
public Future<Integer> calculateSum(int a, int b) {int sum = a + b;return new AsyncResult<Integer>(sum);
}@Test
public void test() throws ExecutionException, InterruptedException {// 使用方法,等待所有线程执行完,在同一处理结果Future<Integer> futureResult = calculateSum(1, 2);//执行其他操作Integer result = futureResult.get(); //阻塞等待异步操作完成并获取结果log.info("result::"+result);
}

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

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

相关文章

字符串操作

字符串是utf-8字符的一个序列&#xff0c;当字符为ascii码时占用1个字节&#xff0c;其他字符则根据需要占用2-4个字节。 go中字符串和c、java、python不同&#xff08;java始终占用2个字节&#xff09;&#xff0c;而同时根据需要占用1-4个字节。 字符串是值类型&#xff0c…

Redis全局命令

"那篝火在银河尽头~" Redis-cli命令启动 现如今&#xff0c;我们已经启动了Redis服务&#xff0c;下⾯将介绍如何使⽤redis-cli连接、操作Redis服务。客户端与服务端交互的方式有两种: ● 第⼀种是交互式⽅式: 后续所有的操作都是通过交互式的⽅式实现&#xff0c;…

Java设计模式:一、六大设计原则-06:依赖倒置原则

文章目录 一、定义&#xff1a;依赖倒置原则二、模拟场景&#xff1a;依赖倒置原则三、违背方案&#xff1a;依赖倒置原则3.1 工程结构3.2 抽奖系统**3.2.1 定义抽奖用户类**3.2.2 抽奖控制 3.3 单元测试 四、改善代码&#xff1a;依赖倒置原则4.1 工程结构4.2 抽奖控制改善4.2…

vnc与windows之间的复制粘贴

【原创】VNC怎么和宿主机共享粘贴板 假设目标主机是linux&#xff0c;终端主机是windows&#xff08;就是在windows上使用VNC登陆linux&#xff09; 在linux中执行 vncconfig -nowin& 在linux选中文字后&#xff0c;无需其他按键&#xff0c;直接在windows中可以黏贴。 …

操作系统备考学习 day1 (1.1.1-1.3.1)

操作系统备考学习 day1 计算机系统概述操作系统的基本概念操作系统的概念、功能和目标操作系统的四个特征并发共享虚拟异步 操作系统的发展和分类操作系统的运行环境操作系统的运行机制 年初做了一个c的webserver 的项目&#xff0c;在学习过程中已经解除部分操作系统的知识&am…

K-Means(K-均值)聚类算法理论和实战

目录 K-Means 算法 K-Means 术语 K 值如何确定 K-Means 场景 美国总统大选摇争取摆选民 电商平台用户分层 给亚洲球队做聚类 ​编辑 其他场景 K-Means 工作流程 K-Means 开发流程 K-Means的底层代码实现 K-Means 的评价标准 K-Means 算法 对于 n 个样本点来说&am…

Golang Gorm 一对多的添加

一对多的添加有两种情况&#xff1a; 一种是添加用户的时候同时创建文章其次是创建文章关联已经存在的用户 添加用户的时候同时创建文章 package mainimport ("gorm.io/driver/mysql""gorm.io/gorm" )// User 用户表 一个用户拥有多篇文章 type User stru…

探索未来金融科技 SCF新加坡举办启动盛会

金融科技的热潮涌向新加坡&#xff0c;令人瞩目的SCF金融公链启动会于8月13日隆重举行。这场盛宴不仅为金融科技领域注入了新的活力&#xff0c;更为广大投资者、合作伙伴以及热衷区块链发展的人士提供了一次宝贵的交流机会。 在SCF金融公链启动会上&#xff0c;William Thomps…

【附安装包】Eplan2022安装教程

软件下载 软件&#xff1a;Eplan版本&#xff1a;2022语言&#xff1a;简体中文大小&#xff1a;1.52G安装环境&#xff1a;Win11/Win10/Win8/Win7硬件要求&#xff1a;CPU2.5GHz 内存4G(或更高&#xff09;下载通道①百度网盘丨64位下载链接&#xff1a;https://pan.baidu.co…

Linux下的系统编程——进程(七)

前言&#xff1a; 程序是指储存在外部存储(如硬盘)的一个可执行文件, 而进程是指处于执行期间的程序, 进程包括 代码段(text section) 和 数据段(data section), 除了代码段和数据段外, 进程一般还包含打开的文件, 要处理的信号和CPU上下文等等.下面让我们开始对Linux进程的学…

Java运行时jar时终端输出的中文日志是乱码

运行Jar时在控制台输出的中文日志全是乱码&#xff0c;这是因为cmd/bash默认的编码是GBK&#xff0c;只要把cmd的编码改成UTF-8即可 两种方式修改&#xff1a;临时修改和注册表永久修改 临时修改 只对当前的cmd页面有效&#xff0c;关闭后重新打开都会恢复成GBK, 打开cmd&am…

【数据结构】十字链表的画法

十字链表的基本概念 有向边又称为弧 假设顶点 v 指向 w&#xff0c;那么 w 称为弧头&#xff0c;v 称为弧尾 顶点节点采用顺序存储 顶点节点 data&#xff1a;存放顶点的信息firstin&#xff1a;指向以该节点为终点&#xff08;弧头&#xff09;的弧节点firstout&#xff1…

gRPC之gRPC认证

1、gRPC认证 前面篇章的gRPC都是明文传输的&#xff0c;容易被篡改数据&#xff0c;本章将介绍如何为gRPC添加安全机制。 gRPC默认内置了两种认证方式&#xff1a; SSL/TLS认证方式 基于Token的认证方式 同时&#xff0c;gRPC提供了接口用于扩展自定义认证方式。 1.1 TLS…

Redis 教程 - Redis 基本操作

Redis 教程 - Redis 基本操作 Redis&#xff08;Remote Dictionary Server&#xff09;是一个开源的内存数据库&#xff0c;它提供了键值对存储和多种数据结构的支持&#xff0c;被广泛应用于缓存、消息队列、计数器等场景。本教程将介绍 Redis 的基本操作&#xff0c;包括连接…

001_C++语法基础

C++语法基础 所有C++语法要用英文区分大小写每个语句写完以分号结束C++标准输入输出头文件iostream 若想通过C++实现数据的输入和输出,需要导入标准输入输出头文件 #include <iostream>标准输入输出头文件<iostream>中包含了cin输入语句和cout输出语句 标准命名…

想系列服务迁移专有云效实操

想系列服务迁移专有云效实操 1注册应用 查看jenkins脚本是否需要修改代码编译路径 gemdale_jenkins/maven3-service/k8s-image/maven3-service-deploy.sh Jenkins上的打包路径 service_tgt_path s e r v i c e w s / t a r g e t / service_ws/target/ servicew​s/target/ser…

前端三大Css处理器之Less

Less是Css预处理器之一&#xff0c;分别有Sass、Less、Stylus这三个。 Lesshttps://lesscss.org/ Less是用JavaScript编写的&#xff0c;事实上&#xff0c;Less是一个JavaScript库&#xff0c;他通过混合、变量、嵌套和规则设置循环扩展了原生普通Css的功能。Less的少数…

k8s之存储篇---存储类StorageClass

介绍 StorageClass 为管理员提供了描述存储"类"的方法。 不同的类型可能会映射到不同的服务质量等级或备份策略&#xff0c;或是由集群管理员制定的任意策略。 Kubernetes 本身并不清楚各种类代表的什么。这个类的概念在其他存储系统中有时被称为"配置文件&quo…

Unity3D Pico VR 手势识别

视频链接 本文章使用的 Unity3D版本: 2021.3.6 , Pico SDK 230 ,Pico OS v.5.7.1 硬件Pico 4 Pico SDK可以去Pico官网下载SDK 导入SDK 第一步&#xff1a;创建Unity3D项目 第二步&#xff1a;导入 PICO Unity Integration SDK 选择 Windows > Package Manager。 …

vue3路由跳转以及传参。和vue2路由跳转传参的区别

路由的安装和引入以及注册就不过多赘述&#xff0c;直接说区别和怎么跳转页面 vue2路由跳转以及传递参数 vue2只需要创建好router文件夹和index.js&#xff0c;配置好我们的路由&#xff0c;在main.js引入 import router from "/router"; // vue路由app.use(route…