java线程池使用
Java线程池的使用
1、 线程池是什么?
线程池(Thread Pool)是一种基于池化思想管理线程的工具,经常出现在多线程服 务器中,如 MySQL。 线程过多会带来额外的开销,其中包括创建销毁线程的开销、调度线程的开销等等, 同时也降低了计算机的整体性能。线程池维护多个线程,等待监督管理者分配可并发 执行的任务。这种做法,一方面避免了处理任务时创建销毁线程开销的代价,另一方 面避免了线程数量膨胀导致的过分调度问题,保证了对内核的充分利用。之前我们在使用多线程都是用 Thread 的 start() 来创建启动一个线程,但是在实际开发中,如果每个请求到达就创建一个新线程,开销是相当大的。服务器在创建和销毁线程上花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用请求的时间和资源要多的多。除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。如果在一个jvm里创建太多的线程,可能会使系统由于过度消耗内存或“切换过度”而导致系统资源不足。这就引入了线程池概念。
优点:
- 降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造 成的损耗。
- 提高响应速度:任务到达时,无需等待线程创建即可立即执行。
- 提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资 源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用 线程池可以进行统一的分配、调优和监控。
- 提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多 的功能。比如延时定时线程池 ScheduledThreadPoolExecutor,就允许任 务延期执行或定期执行。
2、Executors 线程池工具提供四种线程池
ExecutorService ex = Executors.newCachedThreadPool();
ExecutorService ex = Executors.newFixedThreadPool(poolSize)
3、使用线程池的步骤
- 1、创建线程池
- 2、创建任务
- 3、执行任务
- 4、关闭线程池
4、Java 中的线程池核心实现类是 ThreadPoolExecutor
在《阿里巴巴java开发手册》中指出了线程资源必须通过线程池提供,不允许在应用中自行显示的创建线程,这样一方面是线程的创建更加规范,可以合理控制开辟线程的数量;另一方面线程的细节管理交给线程池处理,优化了资源的开销。而线程池不允许使用Executors去创建,而要通过ThreadPoolExecutor方式,这一方面是由于jdk中Executor框架虽然提供了如newFixedThreadPool()、newSingleThreadExecutor()、newCachedThreadPool()等创建线程池的方法,但都有其局限性,不够灵活;另外由于前面几种方法内部也是通过ThreadPoolExecutor方式实现,使用ThreadPoolExecutor有助于大家明确线程池的运行规则,创建符合自己的业务场景需要的线程池,避免资源耗尽的风险。
ThreadPoolExecutor 的继承关系:
ThreadPoolExecutor 实 现 的 顶 层 接 口 是 Executor, 顶 层 接 口 Executor 提 供 了一种思想:将任务提交和任务执行进行解耦。用户无需关注如何创建线程,如 何调度线程来执行任务,用户只需提供 Runnable 对象,将任务的运行逻辑提交 到执行器 (Executor) 中,由 Executor 框架完成线程的调配和任务的执行部分。 ExecutorService 接口增加了一些能力:(1)扩充执行任务的能力,补充可以为一 个或一批异步任务生成 Future 的方法;(2)提供了管控线程池的方法,比如停止线 程池的运行。AbstractExecutorService 则是上层的抽象类,将执行任务的流程 串联了起来,保证下层的实现只需关注一个执行任务的方法即可。最下层的实现类 ThreadPoolExecutor 实现最复杂的运行部分,ThreadPoolExecutor 将会一方面 维护自身的生命周期,另一方面同时管理线程和任务,使两者良好的结合从而执行并 行任务。
ThreadPoolExecutor 是如何运行,如何同时维护线程和执行任务的呢?其运行机制 如下图所示:
线程池在内部实际上构建了一个生产者消费者模型,将线程和任务两者解耦,并不 直接关联,从而良好的缓冲任务,复用线程。线程池的运行主要分成两部分:任务管 理、线程管理。任务管理部分充当生产者的角色,当任务提交后,线程池会判断该任 务后续的流转:(1)直接申请线程执行该任务;(2)缓冲到队列中等待线程执行;(3) 拒绝该任务。线程管理部分是消费者,它们被统一维护在线程池内,根据任务请求进 行线程的分配,当线程执行完任务后则会继续获取新的任务去执行,最终当线程获取 不到任务的时候,线程就会被回收。
ThreadPoolExecutor 的运行状态有 5 种,分别为:
任务执行机制
任务调度
任务调度是线程池的主要入口,当用户提交了一个任务,接下来这个任务将如何执行 都是由这个阶段决定的。了解这部分就相当于了解了线程池的核心运行机制。 首先,所有任务的调度都是由 execute 方法完成的,这部分完成的工作是:检查现在 线程池的运行状态、运行线程数、运行策略,决定接下来执行的流程,是直接申请线 程执行,或是缓冲到队列中执行,亦或是直接拒绝该任务。其执行过程如下:
1. 首先检测线程池运行状态,如果不是 RUNNING,则直接拒绝,线程池要保 证在 RUNNING 的状态下执行任务。
2. 如果 workerCount < corePoolSize,则创建并启动一个线程来执行新提 交的任务。
3. 如果 workerCount >= corePoolSize,且线程池内的阻塞队列未满,则将任 务添加到该阻塞队列中。
4. 如 果 workerCount >= corePoolSize && workerCount < maximumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提 交的任务。
5. 如果 workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满 , 则根据拒绝策略来处理该任务 , 默认的处理方式是直接抛异常。
其执行流程如下图所示:
任务缓冲
任务缓冲模块是线程池能够管理任务的核心部分。线程池的本质是对任务和线程的管 理,而做到这一点最关键的思想就是将任务和线程两者解耦,不让两者直接关联,才 可以做后续的分配工作。线程池中是以生产者消费者模式,通过一个阻塞队列来实现 的。阻塞队列缓存任务,工作线程从阻塞队列中获取任务。 阻塞队列 (BlockingQueue) 是一个支持两个附加操作的队列。这两个附加的操作是: 在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程 会等待队列可用。阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元 素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。下图中展示了线程 1 往阻塞队列中添加元素,而线程 2 从阻塞队列中移除元素:
使用不同的队列可以实现不一样的任务存取策略。在这里,我们可以再介绍下阻塞队 列的成员:
任务拒绝
任务拒绝模块是线程池的保护部分,线程池有一个最大的容量,当线程池的任务缓存 队列已满,并且线程池中的线程数目达到 maximumPoolSize 时,就需要拒绝掉该任务,采取任务拒绝策略,保护线程池。 拒绝策略是一个接口,其设计如下:
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
用户可以通过实现这个接口去定制拒绝策略,也可以选择 JDK 提供的四种已有拒绝 策略,其特点如下:
ThreadPoolExcutor构造函数的定义:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) ;
- corePoolSize
线程池大小,决定着新提交的任务是新开线程去执行还是放到任务队列中,也是线程池的最最核心的参数。一般线程池开始时是没有线程的,只有当任务来了并且线程数量小于corePoolSize才会创建线程。
1.核心线程会一直存活,及时没有任务需要执行
2.当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理
3.设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭。
- maximumPoolSize
线程池中允许的最大线程数,线程池中的当前线程数目不会超过该值。如果队列中任务已满,并且当前线程个数小于maximumPoolSize,那么会创建新的线程来执行任务。这里值得一提的是largestPoolSize,该变量记录了线程池在整个生命周期中曾经出现的最大线程个数。为什么说是曾经呢?因为线程池创建之后,可以调用setMaximumPoolSize()改变运行的最大线程的数目。
1.当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务
2.当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常
- keepAliveTime
在线程数量超过corePoolSize后,多余空闲线程的最大存活时间。
1.当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
2.如果allowCoreThreadTimeout=true,则会直到线程数量=0
- unit
keepAliveTime的时间单位 - workQueue
存放来不及处理的任务的队列,是一个BlockingQueue。 - threadFactory
生产线程的工厂类,可以定义线程名,优先级等。 - handler 拒绝策略,当任务来不及处理的时候,如何处理。
两种情况会拒绝处理任务:
1.当线程数已经达到maxPoolSize,切队列已满,会拒绝新任务
2.当线程池被调用shutdown()后,会等待线程池里的任务执行完毕,再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务。
线程池会调用rejectedExecutionHandler来处理这个任务。如果没有设置,默认是AbortPolicy,会抛出异常。
ThreadPoolExecutor类有几个内部实现类来处理拒绝任务:
- 1.AbortPolicy 丢弃任务,抛运行时异常
- 2.CallerRunsPolicy 执行任务
- 3.DiscardPolicy 忽视,什么都不会发生
- 4.DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务
- 5.实现RejectedExecutionHandler接口,可自定义处理器
二:ThreadPoolExecutor执行顺序:
- 1.当线程数小于核心线程数时,创建线程。
- 2.当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
- 3.当线程数大于等于核心线程数,且任务队列已满
- 3.1若线程数小于最大线程数,创建线程
- 3.2若线程数等于最大线程数,抛出异常,拒绝任务
三:线程池参数的合理设置
为了说明合理设置的条件,我们首先确定有以下几个相关参数:
1.tasks,程序每秒需要处理的最大任务数量(假设系统每秒任务数为100~1000)
2.tasktime,单线程处理一个任务所需要的时间(每个任务耗时0.1秒)
3.responsetime,系统允许任务最大的响应时间(每个任务的响应时间不得超过2秒)
corePoolSize
每个任务需要tasktime秒处理,则每个线程每秒可处理1/tasktime个任务。系统每秒有tasks个任务需要处理,则需要的线程数为:tasks/(1/tasktime)。
即tasks*tasktime个线程数。假设系统每秒任务数为100到1000之间,每个任务耗时0.1秒,则需要100x0.1至1000x0.1,即10到100个线程。那么corePoolSize应该设置为大于10。
具体数字最好根据80、20原则,即80%情况下系统每秒任务数,若系统80%的情况下任务数小于200,最多时为1000,则corePoolSize可设置为20。
queueCapacity:任务队列的长度
任务队列的长度要根据核心线程数,以及系统对任务响应时间的要求有关。队列长度可以设置为(corePoolSize/tasktime)responsetime: (20/0.1)2=400,即队列长度可设置为400。
如果队列长度设置过大,会导致任务响应时间过长,如以下写法:
LinkedBlockingQueue queue = new LinkedBlockingQueue();
这实际上是将队列长度设置为Integer.MAX_VALUE,将会导致线程数量永远为corePoolSize,再也不会增加,当任务数量陡增时,任务响应时间也将随之陡增。
maxPoolSize:最大线程数
当系统负载达到最大值时,核心线程数已无法按时处理完所有任务,这时就需要增加线程。每秒200个任务需要20个线程,那么当每秒达到1000个任务时,则需要(1000-queueCapacity)*(20/200),即60个线程,可将maxPoolSize设置为60。
keepAliveTime:
线程数量只增加不减少也不行。当负载降低时,可减少线程数量,如果一个线程空闲时间达到keepAliveTiime,该线程就退出。默认情况下线程池最少会保持corePoolSize个线程。keepAliveTiime设定值可根据任务峰值持续时间来设定。
以上关于线程数量的计算并没有考虑CPU的情况。若结合CPU的情况,比如,当线程数量达到50时,CPU达到100%,则将maxPoolSize设置为60也不合适,此时若系统负载长时间维持在每秒1000个任务,则超出线程池处理能力,应设法降低每个任务的处理时间(tasktime)。
有界的任务队列:有界的任务队列可以使用ArrayBlockingQueue实现
pool = new ThreadPoolExecutor(1,
2,
1000,
TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(10),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
无界的任务队列:有界任务队列可以使用LinkedBlockingQueue实现,如下所示
pool = new ThreadPoolExecutor(1,
2,
1000,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
自定义拒绝策略
pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(5),
Executors.defaultThreadFactory(), new RejectedExecutionHandler() {
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println(r.toString()+"执行了拒绝策略");
}
});
5、全局线程池
public class GlobalThreadPool {
/**
* 核心线程大小
* 程序每秒需要处理的最大任务数量(10) * 单线程处理一个任务所需要的时间(1s)
*/
private static final Integer CORE_POOL_SIZE = 10;
/**
* 最大线程大小
*/
private static final Integer MAXIMUM_POOL_SIZE = 20;
/**
* 检测空闲线程的时间周期
*/
private static final Long KEEP_ALIVE_TIME = 20L;
/**
* 阻塞队列大小
* (corePoolSize/taskTime) * responseTime
*/
private static final Integer BLOCK_QUEUE_SIZE = 2000;
private static final ThreadPoolExecutor tpe = new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE,
KEEP_ALIVE_TIME,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(BLOCK_QUEUE_SIZE));
public static Future submitTask(Callable task){
return tpe.submit(task);
}
public static ThreadPoolExecutor getExecutor() {
return tpe;
}
}
6、创建任务:
任务分为两种:一种是有返回值的( callable ),一种是没有返回值的( runnable ). Callable与 Future 两功能是Java在后续版本中为了适应多并法才加入的,Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其他线程执行的任务。
- 无返回值的任务就是一个实现了runnable接口的类.使用run方法.
- 有返回值的任务是一个实现了callable接口的类.使用call方法.
Callable和Runnable的区别如下:
- Callable定义的方法是call,而Runnable定义的方法是run。
- Callable的call方法可以有返回值,而Runnable的run方法不能有返回值。
- Callable的call方法可抛出异常,而Runnable的run方法不能抛出异常。
7、执行任务
通过java.util.concurrent.ExecutorService接口对象来执行任务,该对象有两个方法可以执行任务execute和submit。execute这种方式提交没有返回值,也就不能判断是否执行成功。submit这种方式它会返回一个Future对象,通过future的get方法来获取返回值,get方法会阻塞住直到任务完成。
execute与submit区别:
- 接收的参数不一样
- submit有返回值,而execute没有
- submit方便Exception处理
- execute是Executor接口中唯一定义的方法;submit是ExecutorService(该接口继承Executor)中定义的方法
8、使用线程池
(1)调用Runnable
pool.execute(new Runnable() {
@Override
public void run() {
// 输出内容:MyThreadFactory_testThread_0
System.out.println(Thread.currentThread().getName());
}
});
(2)调用callable
Future<String> future = pool.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return Thread.currentThread().getName();
}
});
9、Spring 提供的线程池:ThreadPoolTaskExecutor
/**
* @Author dw
* @ClassName GlobalThreadPool
* @Description 全局线程池配置
* @Date 2022/5/26 15:08
* @Version 1.0
*/
@Configuration
public class GlobalThreadPool {
private static final Logger logger = LoggerFactory.getLogger(GlobalThreadPool.class);
private static final Integer CORE_POOL_SIZE = 5;
private static final Integer QUEUE_SIZE = 10;
@Bean
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor threadPoolExecutor = new ThreadPoolTaskExecutor();
// 1、cpu密集型 cpu+1; 2、IO密集型 cpu*2 + 1
int maxPoolSize = Runtime.getRuntime().availableProcessors() * 2;
logger.info("ThreadPoolTaskExecutor-CORE_POOL_SIZE: {}", CORE_POOL_SIZE);
logger.info("ThreadPoolTaskExecutor-MAX_POOL_SIZE: {}", maxPoolSize);
threadPoolExecutor.setCorePoolSize(CORE_POOL_SIZE);
threadPoolExecutor.setMaxPoolSize(maxPoolSize);
threadPoolExecutor.setQueueCapacity(QUEUE_SIZE);
threadPoolExecutor.setKeepAliveSeconds(60);
threadPoolExecutor.setThreadNamePrefix("executor-custom-");
threadPoolExecutor.setAllowCoreThreadTimeOut(false);
threadPoolExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
return threadPoolExecutor;
}
}
10、线程池在业务中的实践
业务背景
在当今的互联网业界,为了最大程度利用 CPU 的多核性能,并行运算的能力是不可 或缺的。通过线程池管理线程获取并发性是一个非常基础的操作,让我们来看两个典 型的使用线程池获取并发性的场景。
场景 1:快速响应用户请求 描述:用户发起的实时请求,服务追求响应时间。比如说用户要查看一个商品的信 息,那么我们需要将商品维度的一系列信息如商品的价格、优惠、库存、图片等等聚 合起来,展示给用户。 分析:从用户体验角度看,这个结果响应的越快越好,如果一个页面半天都刷不出, 用户可能就放弃查看这个商品了。而面向用户的功能聚合通常非常复杂,伴随着调用 与调用之间的级联、多级级联等情况,业务开发同学往往会选择使用线程池这种简单 的方式,将调用封装成任务并行的执行,缩短总体响应时间。另外,使用线程池也是 有考量的,这种场景最重要的就是获取最大的响应速度去满足用户,所以应该不设置 队列去缓冲并发任务,调高 corePoolSize 和 maxPoolSize 去尽可能创造多的线程 快速执行任务。
场景 2:快速处理批量任务
描述:离线的大量计算任务,需要快速执行。比如说,统计某个报表,需要计算出全 国各个门店中有哪些商品有某种属性,用于后续营销策略的分析,那么我们需要查询 全国所有门店中的所有商品,并且记录具有某属性的商品,然后快速生成报表。 分析:这种场景需要执行大量的任务,我们也会希望任务执行的越快越好。这种情况 下,也应该使用多线程策略,并行计算。但与响应速度优先的场景区别在于,这类 场景任务量巨大,并不需要瞬时的完成,而是关注如何使用有限的资源,尽可能在 单位时间内处理更多的任务,也就是吞吐量优先的问题。所以应该设置队列去缓冲 并发任务,调整合适的 corePoolSize 去设置处理任务的线程数。在这里,设置的线 程数过多可能还会引发线程上下文切换频繁的问题,也会降低处理任务的速度,降低 吞吐量。
11、CPU和线程概述、线程池如何合理设置
物理核
- 物理核数量=cpu数(机子上装的cpu的数量)*每个cpu的核心数
虚拟核
- 所谓的4核8线程,4核指的是物理核心。通过超线程技术,用一个物理核模拟两个虚拟核,每个核两个线程,总数为8线程。
- 在操作系统看来是8个核,但是实际上是4个物理核。
- 通过超线程技术可以实现单个物理核实现线程级别的并行计算,但是比不上性能两个物理核。
单核cpu和多核cpu
- 都是一个cpu,不同的是每个cpu上的核心数
- 多核cpu是多个单核cpu的替代方案,多核cpu减小了体积,同时也减少了功耗
- 一个核心只能同时执行一个线程
进程和线程
- 进程是操作系统进行资源(包括cpu、内存、磁盘IO等)分配的最小单位
- 线程是cpu调度和分配的基本单位
- 资源分配给进程,线程共享进程资源。
对比 进程 线程
- 定义 进程是程序运行的一个实体的运行过程,是系统进行资源分配和调配的一个独立单位 线程是进程运行和执行的最小调度单位
- 系统开销 创建撤销切换开销大,资源要重新分配和收回 仅保存少量寄存器的内容,开销小,在进程的地址空间执行代码
- 拥有资产 资源拥有的基本单位 基本上不占资源,仅有不可少的资源(程序计数器,一组寄存器和栈)
- 调度 资源分配的基本单位 独立调度分配的单位
- 安全性 进程间相互独立,互不影响 线程共享一个进程下面的资源,可以互相通信和影响
- 地址空间 系统赋予的独立的内存地址空间 由相关堆栈寄存器和和线程控制表TCB组成,寄存器可被用来存储线程内的局部变量
线程切换
- cpu给线程分配时间片(也就是分配给线程的时间),执行完时间片后会切换都另一个线程。
- 切换之前会保存线程的状态,下次时间片再给这个线程时才能知道当前状态。
- 从保存线程A的状态再到切换到线程B时,重新加载线程B的状态的这个过程就叫上下文切换。
- 上下切换时会消耗大量的cpu时间。
线程开销
- 上下文切换消耗
- 线程创建和消亡的开销
- 线程需要保存维持线程本地栈,会消耗内存
串行、并发、并行
串行
- 多个任务,执行时一个执行完再执行另一个。
- 比喻:吃完饭再看球赛。
并发
- 多个线程在单个核心运行,同一时间一个线程运行,系统不停切换线程,看起来像同时运行,实际上是线程不停切换。
- 比喻: 一会跑去食厅吃饭,一会跑去客厅看球赛。
并行
- 每个线程分配给独立的核心,线程同时运行。
- 比喻:一边吃饭一边看球赛。
多核下线程数量选择
计算密集型
- 程序主要为复杂的逻辑判断和复杂的运算。
- cpu的利用率高,不用开太多的线程,开太多线程反而会因为线程切换时切换上下文而浪费资源。
IO密集型
- 程序主要为IO操作,比如磁盘IO(读取文件)和网络IO(网络请求)。
- 因为IO操作会阻塞线程,cpu利用率不高,可以开多点线程,阻塞时可以切换到其他就绪线程,提高cpu利用率。
提高性能的方向
- 提高性能的一种方式:提高硬件水平,处理速度或核心数。
- 另一种方式:根据场景,合理设置线程数,软件上提高cpu利用率。(线程数的设置,如果是CPU密集型应用,则线程池大小设置为N+1,如果是IO密集型应用,则线程池大小设置为2N+1)
- 线程数的设置也可参考公式线程数=CPU核心数/(1-阻塞系数),看你的任务是计算密集性的还是IO密集性的,IO密集型的,阻塞系数加大,计算密集型的阻塞系数减少,阻塞系数可以将其理解为>阻塞时间/计算时间;
并发编程网上关于线程池的总结
高并发、任务执行时间短的业务怎样使用线程池?
并发不高、任务执行时间长的业务怎样使用线程池?并发高、业务执行时间长的业务怎样使用线程池?
- 高并发、任务执行时间短的业务,线程池线程数可以设置为CPU核数+1,减少线程上下文的切换
- 并发不高、任务执行时间长的业务要区分开看:
- 假如是业务时间长集中在IO操作上,也就是IO密集型的任务,因为IO操作并不占用CPU,所以不要让所有的CPU闲下来,可以适当加大线程池中的线程数目,让CPU处理更多的业务
- 假如是业务时间长集中在计算操作上,也就是计算密集型任务,线程池中的线程数设置得少一些,减少线程上下文的切换
- 并发高、业务执行时间长,这种类型任务需要分段定位优化,第一看看这些业务里面某些数据是否能做缓存,其二增加服务器提高硬件性能,其三是参考设置合理的线程池,最后,业务执行时间长的问题,需要考虑进行业务的拆分和解耦。