一、写在最前面
十多年前计算硬件还不算发达,那个时候单核电脑想要实现多线程,需要CPU来回切换,就像一个马戏团的小丑同时往上抛多个球,然后再快速不停的接住每个球,让人感觉实现了多线程,然而实则是假的多线程。随着计算机硬件的迅速发展,多核CPU逐渐成为了主流,所以多个线程各自跑在独立的CPU上,不需要再来回快速切换,这样可以极大的提高程序运行的效率。
二、什么是线程池
线程池(Thread Pool)是一种基于池化思想管理线程的工具,经常使用在多核服务器中,简而言之就是预制一定数量的线程在池里,当有程序申请一个线程时,从池里拿出一个用完之后再放回池里。这样可以避免线程频繁的创建、销毁、调度线降低服务器的整体性能。一方面避免了处理任务时创建销毁线程开销的代价,另一方面避免了线程数量膨胀导致的过分调度问题,保证了对内核的充分利用。
三、线程池工作原理及优点
线程池做的工作就是控制运行的线程数量,处理过程中将任务放进队列,然后在线程创建后启动这些任务,如果线程数量超过了最大线程数,超出数量的线程就在在阻塞队列中进行排队等其他线
程执行完毕,再从队列中取出任务来执行。
线程池最大特点在于:
1.降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的性能消耗
2.提高响应速度:当任务到达时,任务可以不需要等待线程创建就能立即执行
3.提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池路进行统一的分配,调优和监控
四、线程池实现
(1)Java中的线程池核心实现类是ThreadPoolExecutor,基于JDK 1.8的源码来分析Java线程池的核心设计与实现。先来看一下ThreadPoolExecutor的类图,了解到ThreadPoolExecutor的继承关系。
ThreadPoolExecutor实现的顶层接口是Executor,由JDK源码可知在接口Executor中用户无需关注如何创建线程,如何调度线程来执行任务,用户只需提供Runnable对象,将任务的运行逻辑提交到执行器Executor中,由Executor框架完成线程的调配和任务的执行部分。
ExecutorService接口相对于Executor接口:
1.充执行任务的能力,补充可以为一个或一批异步任务生成Future的方法;
2.提供了管控线程池的方法,比如停止线程池的运行。
AbstractExecutorService则是上层的抽象类,将执行任务的流程串联了起来,保证下层的实现只需关注一个执行任务的方法即可。
最下层的实现类ThreadPoolExecutor实现最复杂的运行部分:
1.可以自动创建、管理和复用指定数量的一组线程,适用方只需提交任务即可
2.线程安全,ThreadPoolExecutor内部有状态、核心线程数、非核心线程等属性,广泛使用了CAS和AQS锁机制避免并发带来的冲突问题
3.提供了核心线程、缓冲阻塞队列、非核心线程、抛弃策略的概念,可以根据实际应用场景进行组合使用
4.提供了beforeExecute 和afterExecute()可以支持对线程池的功能进行扩展
ThreadPoolExecutor 线程池运行执行流程图:
线程池在内部实际上构建了一个生产者消费者模型,将线程和任务两者解耦,并不直接关联,从而良好的缓冲任务,复用线程。
线程池的运行主要分成两部分:任务管理、线程管理。任务管理部分充当生产者的角色,当任务提交后,线程池会判断该任务后续的流转:
(1)直接申请线程执行该任务;
(2)缓冲到队列中等待线程执行;
(3)拒绝该任务。线程管理部分是消费者,它们被统一维护在线程池内,根据任务请求进行线程的分配,当线程执行完任务后则会继续 获取新的任务去执行,最终当线程获取不到任务的时候,线程就会被回收。
五、线程池创建与使用
1、创建线程的三个方法
newSingleThreadExecutor(); 创建单个线程的线程池方法,它只会用唯一的工作线程来执行任务,能够保证所有任务按照指定的顺序执行
public static void main(String[] args) {
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();//单个线程
try {
for (int i = 1; i <= 10; i++) {
newSingleThreadExecutor.execute(() -> {
System.out.println(Thread.currentThread().getName() + " OK");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//线程池用完,程序结束,关闭线程池
newSingleThreadExecutor.shutdown();
}
}
}
newFixedThreadPool(5); 创建一个固定线程池大小,可以控制最大并发数超出线程会在队列中进行排队
public static void main(String[] args) {
ExecutorService newSingleThreadExecutor = Executors.newFixedThreadPool(5);//创建一个固定线程池大小
try {
for (int i = 1; i <= 10; i++) {
newSingleThreadExecutor.execute(() -> {
System.out.println(Thread.currentThread().getName() + " OK");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//线程池用完,程序结束,关闭线程池
newSingleThreadExecutor.shutdown();
}
}
}
newCachedThreadPool();创建一个可以的缓存线程池,如果线程池长度超过处理需要,可以灵活回收空闲线程,若无可回收,则新建线程
public static void main(String[] args) {
ExecutorService newSingleThreadExecutor = Executors.newCachedThreadPool();//创建一个固定线程池大小
try {
for (int i = 1; i <= 10; i++) {
newSingleThreadExecutor.execute(() -> {
System.out.println(Thread.currentThread().getName() + " OK");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//线程池用完,程序结束,关闭线程池
newSingleThreadExecutor.shutdown();
}
}
}
然而实际开发中并不会Executors去创建线程池,那究竟是为什么呢?《阿里巴巴Java开发手册》中对这个原因有了详细说明:
查看这三个方法对源码也可以得知,通过源码可以了解到这三个创建线程池的方法就是调用了底层的ThreadPoolExecutor()的构造方法创建的线程,所以其本质还是ThreadPoolExecutor(),实际开发中若要使用线程池,那么就得自己去手动创建创建线程池。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1,
1,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads,
nThreads,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0,
Integer.MAX_VALUE,
60L,
TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
那么再看下ThreadPoolExecutor()的7参构造方法:
public ThreadPoolExecutor(int corePoolSize, //核心线程池大小,相当于银行常驻窗口
int maximumPoolSize, //最大核心线程池大小,银行最大能开的窗口数
long keepAliveTime, //超时了没有人调用就会释放多余线程,只剩下核心线程
TimeUnit unit, //超时单位
BlockingQueue<Runnable> workQueue, //阻塞队列,相当于银行侯客区,这里满了就会采用拒绝策略
ThreadFactory threadFactory, //线程工厂,创建线程的,一般不用动,默认
RejectedExecutionHandler handler) { //拒绝策略,当阻塞队列满了,就会拒绝
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
2、创建线程参数
对参数的解释:
corePoolSize:核心线程数,当一个新任务被提交到池中,如果当前运行线程小于核心线程数(corePoolSize),即使当前有空闲线程,也会新建一个线程来处理新提交的任务就是即使这些线程属于空闲时间,也要保存在池中,不能被回收。
maximumPoolSize:最大核心线程数,如果当前运行线程数大于核心线程数(corePoolSize)并小于最大线程数(maximumPoolSize),只有当阻塞队列已满的情况下才会新建线程。
keepAliveTime:keepAliveTime 为超过 corePoolSize 线程数量的线程最大空闲时间。
unit:超时的时间单位
workQueue:任何阻塞队列(BlockingQueue)都可以用来转移或保存提交的任务,线程池大小和阻塞队列相互约束线程池。如果运行线程数小于corePoolSize,提交新任务时就会新建一个线程来运行;如果运行线程数大于或等于corePoolSize,新提交的任务就会入列等待;如果队列已满,并且运行线程数小于maximumPoolSize,也将会新建一个线程来运行;如果线程数大maximumPoolSize,新提交的任务将会根据拒绝策略来处理。
threadFactory:执行程序创建新线程时使用的工厂,默认使用DefaultThreadFactory,也可以自定义
handler:四种拒绝策略,具体在下文中有介绍
3、自定义线程池
由《阿里巴巴Java开发手册》可知,在实际开发中,线程资源必须使用线程池提供,不能在应用中自行显示创建线程,具体原因可以参考下面《阿里巴巴Java开发手册》的原因:
其中线程池分为CPU密集型任务与IO密集型任务,下面可以看下他们的区别:
CPU密集型任务:
尽量使用较小的线程池,一般为CPU核心数+1。 因为CPU密集型任务使得CPU使用率很高,若开过多的线程数,会造成CPU过度切换。
IO密集型任务:
可以使用稍大的线程池,一般为2*CPU核心数。 IO密集型任务CPU使用率并不高,因此可以让CPU在等待IO的时候有其他线程去处理别的任务,充分利用CPU时间。
这里自定义线程池采用CPU密集型:
package com.haha.threadpool;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 先定义一个线程池的枚举类,这里采用CPU密集型,这里定义最大线程是就为CPU的核数
*/
@Getter
@AllArgsConstructor
public enum CustomExecutorServiceEnum {
THREADPOOL_ARGS(1,1,10,20,"适合并发量不是很大场景的公用线程池"),
;
/**
* 核心线程数(最终核心线程数 = corePoolSize * 核数)
**/
private int corePoolSize;
/**
* 最大线程数(最终最大线程数 = maximumPoolSize * 核数)
**/
private int maximumPoolSize;
/**
* idle线程最大存活时间 (单位:TimeUnit.SECONDS)
**/
private long keepAliveTime;
/**
* 阻塞线程池的最大容量
**/
private int blockQueuecapacity;
/**
* 适用的场景
**/
private String description;
}
自定义线程池:
package com.haha.threadpool;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
import java.util.concurrent.*;
/**
* 自定义线程池
**/
@Slf4j
public class CustomExecutorFactory {
private static final Map<CustomExecutorServiceEnum, ExecutorService> executorServiceMap = new ConcurrentHashMap<>();
private static final String CUSTOM_LOCK = "自定义线程池";
public static ExecutorService get(CustomExecutorServiceEnum typeEnum) {
if(Objects.isNull(typeEnum)){
throw new IllegalArgumentException("ExecutorServiceTypeEnum 不能为null.");
}
ExecutorService customExecutor = executorServiceMap.get(typeEnum);
if (Objects.isNull(customExecutor)) {
synchronized (CUSTOM_LOCK) {
customExecutor = executorServiceMap.get(typeEnum);
if (Objects.isNull(customExecutor)) {
customExecutor = buildExecutor(typeEnum);
executorServiceMap.put(typeEnum, customExecutor);
}
}
}
return customExecutor;
}
/**
* 自定义线程池,构建线程池参数
*
* @param typeEnum
* @return
*/
private static ExecutorService buildExecutor(CustomExecutorServiceEnum typeEnum) {
//获得机器的cpu核数
int numberOfProcessors = Runtime.getRuntime().availableProcessors();
//设置核心线程数
int corePoolSize = numberOfProcessors * typeEnum.getCorePoolSize();
//设置最大线程数
int maximumPoolSize = numberOfProcessors * typeEnum.getMaximumPoolSize();
//设置线程最大存活时间
long keepAliveTime = typeEnum.getKeepAliveTime();
//设置阻塞队列的容量
BlockingQueue<Runnable> linkedBlockingQueue = new LinkedBlockingQueue<>(typeEnum.getBlockQueuecapacity());
ThreadFactory threadFactory = new BasicThreadFactory.Builder().namingPattern(typeEnum.name() + "-%d").daemon(true).build();
ExecutorService executorService;
try {
//构造线程池, 采用默认拒绝策略
executorService = new ThreadPoolExecutor(corePoolSize,
maximumPoolSize,
keepAliveTime,
TimeUnit.MILLISECONDS,
linkedBlockingQueue,
threadFactory,
new ThreadPoolExecutor.AbortPolicy());
//ttl转化
executorService = TtlExecutors.getTtlExecutorService(executorService);
// 注册jvm关闭钩子
shutdownByCustom(executorService, typeEnum);
} catch (Exception e) {
log.error("{} 线程池初始化异常 init error[{}]", typeEnum.name(), e);
throw new ExceptionInInitializerError(e);
}
return executorService;
}
/**
* 优雅优雅shutdown
*
* @param result
* @param typeEnum
*/
private static void shutdownByCustom(final ExecutorService result, final CustomExecutorServiceEnum typeEnum) {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
log.info("{} executorService 服务正在关闭", typeEnum.name());
result.shutdown();
try {
// 一秒后直接关闭
if (!result.awaitTermination(1, TimeUnit.SECONDS)) {
log.info("{} 等待超时,Executor服务立即关闭.", typeEnum.name());
result.shutdownNow();
}
} catch (InterruptedException e) {
log.error("{} executorService 服务关闭中断.", typeEnum.name());
result.shutdownNow();
}
log.info("{} executorService 服务完成关闭.", typeEnum.name());
}));
}
}
按照这种方式创建线程池有以下优点
1.在工程中统一获取线程池,便于管理
2.支持TTL转化,threadlocal不丢,traceId不断
3.可以优雅shutdown
六、线程池的生命周期
线程池运行的状态,并不是用户显式设置的,而是伴随着线程池的运行,由内部来维护。线程池内部使用一个变量维护两个值:运行状态(runState)和线程数量 (workerCount)。线程池将运行状态(runState)、线程数量 (workerCount)两个关键参数的维护放在了一起,
//源代码:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
变量ctl是个AtomicInteger类型的数据,它能够有效的控制线程池的运行状态和有效的线程数量,包含了两个数据信息,线程池的运行状态runState和线程池内有效的线程数量workerCount高3位保存runState,低29位保存workerCount,两个变量之间互不干扰。用一个变量去存储两个值,可避免在做相关决策时,出现不一致的情况,不必为了维护两者的一致,而占用锁资源。
源码分析:
AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
int COUNT_BITS = Integer.SIZE - 3; // 29
int CAPACITY = (1 << COUNT_BITS) - 1; // 二进制表示是:29个1 (1 1111 1111 1111 1111 1111 1111 1111)
//线程池一共有5个运行状态,jdk1.8的源码中用111表示为-1,000表示为0,001表示为1,010表示为2,011表示为3,则对应的位运算如下图所示,
// 用高3位来表示运行状态
// -1的二进制位全是1,然后左移29位,最后结果是0-29位是0,30位以上都是1,二进制:1110 0000 0000 0000 0000 0000 0000 0000
private static final int RUNNING = -1 << COUNT_BITS;
// 0 左移29位还是0
private static final int SHUTDOWN = 0 << COUNT_BITS;
// 1左移29位,结果就是第30位是1,二进制:0010 0000 0000 0000 0000 0000 0000 0000
private static final int STOP = 1 << COUNT_BITS;
// 2的二进制是10,左移29位,结果就是第31位是1,二进制:0100 0000 0000 0000 0000 0000 0000 0000
private static final int TIDYING = 2 << COUNT_BITS;
// 3的二进制是11,左移29位,结果就是第30和31位是1,二进制:0110 0000 0000 0000 0000 0000 0000 0000
private static final int TERMINATED = 3 << COUNT_BITS;
//计算当前运行状态,取高三位
private static int runStateOf(int c) { return c & ~CAPACITY; }
//计算当前线程数量,取低29位
private static int workerCountOf(int c) { return c & CAPACITY; }
//通过状态和线程数生成ctl
private static int ctlOf(int rs, int wc) { return rs | wc; }
线程生命周期转换如下:
ThreadPoolExecutor的运行状态有5种,分别为:
RUNNING:表示可以接收新的任务,并且可以处理阻塞队列中的任务
SHUTDOWN:关闭状态,不能接收新提交的任务,但可以继续处理阻塞队列中的已存在的任务
STOP:不再接收新的任务,也不处理队列中的任务,并且会中断正在处理任务的线程
TIDYING:所有任务已经终止,且工作线程数量(workCount)为0。最后变迁到这个状态的线程将要执行terminated()钩子方法,只会有一个线程执行这个方法
TERMINATED:中止状态,已经执行完terminated()钩子方法;
其中上图中的shutdown()与shutdownNow()存在一点差别:
shutdown():停止线程方法,不能接受新的任务,但是却可以处理阻塞队列中已存在的任务
shutdownNow():停止线程方法,不能接受新的任务,也不处理阻塞队列中已存在的任务,并且尝试将正在执行的任务interrupt中断
七、线程池的任务调度机制
1、由开篇的线程池执行流程图可知,任务调度是线程池的主要入口,当用户提交了一个任务,接下来这个任务将如何执行都是由这个任务调度实现的。
(1)先检测线程池运行状态,如果不是RUNNING,则直接拒绝,线程池要保证在RUNNING的状态下执行任务。
(2)若工作线程数workerCount < 核心线程数corePoolSize,则创建并启动一个线程来执行新提交的任务。
(3)若工作线程数workerCount >= 核心线程数corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中。
(4)若工作线程数workerCount >= 核心线程数corePoolSize && 工作线程数workerCount < 最大线程数maximumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务。
(5)若工作线程数workerCount >= 最大线程数maximumPoolSize,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常。
其流程图如下:
2、其中阻塞队列可以分为以下几种:
直接提交队列:使用SynchronousQueue队列,提交的任务不会被保存,总是会马上提交执行。如果用于执行任务的线程数量小于maximumPoolSize,则尝试创建新的进程,如果达到maximumPoolSize设置的最大值,则根据你设置的handler执行拒绝策略。
有界的任务队列:使用ArrayBlockingQueue有界任务队列时,当有任务需要执行时,线程池创建的线程数达到最大核心数corePoolSize时,此时就会将新的任务加到队列ArrayBlockingQueue中去,当任务数达到ArrayBlockingQueue队列的最大容量时,CPU就会开启最大线程数处理。这种情况下如何让设置最大线程数就变得难以控制了,因为使用大的队列容量和小的线程数可以减少CPU使用率、系统资源和上下文切换的开销,但是会导致吞吐量变低,如果任务频繁地阻塞(例如被I/O限制),系统就能为更多的线程调度执行时间。使用小的队列通常需要更多的线程数,这样可以最大化CPU使用率,但可能会需要更大的调度开销,从而降低吞吐量。
无界的任务队列:无界任务队列可以使用LinkedBlockingQueue实现,使用无界任务队列,线程池的任务队列可以无限制的添加新的任务,而线程池创建的最大线程数量就是你corePoolSize设置的数量,也就是说在这种情况下maximumPoolSize这个参数是无效的,哪怕你的任务队列中缓存了很多未执行的任务当线程池的线程数达到corePoolSize后,就不会再增加了;若后续有新的任务加入,则直接进入队列等待,这种阻塞队列要注意控制好任务的提交速度与任务的处理速度,不然就又可能造成资源耗尽的问题。其实LinkedBlockingQueue也是可以设置界限的,它默认的界限是Integer.MAX_VALUE。同时也支持也支持构造的时候设置队列大小。
优先任务队列:优先任务队列通过PriorityBlockingQueue实现,PriorityBlockingQueue其实是一个特殊的无界队列,它其中无论添加了多少个任务,线程池创建的线程数也不会超过corePoolSize的数量,只不过其他队列一般是按照先进先出的规则处理任务,而PriorityBlockingQueue队列可以自定义规则根据任务的优先级顺序先后执行。
3、四种拒绝策略:
AbortPolicy线程池默认的拒绝策略,线程池满了就直接抛出异常
//看下AbortPolicy的源码
public static class AbortPolicy implements RejectedExecutionHandler {
/**
* Creates an {@code AbortPolicy}.
*/
public AbortPolicy() { }
/**
* Always throws RejectedExecutionException.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
* @throws RejectedExecutionException always
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
CallerRunsPolicy:当线程池满了,就让让提交者执行提交任务,能够减缓新任务的提交速度。这种情况是需要让所有的任务都执行完毕。简单说就是请求从哪里来的就让其回哪里去
public static class CallerRunsPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code CallerRunsPolicy}.
*/
public CallerRunsPolicy() { }
/**
* Executes task r in the caller's thread, unless the executor
* has been shut down, in which case the task is discarded.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
DiscardPolicy:队列满了就直接丢掉任务,不会抛出异常
public static class DiscardPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code DiscardPolicy}.
*/
public DiscardPolicy() { }
/**
* Does nothing, which has the effect of discarding task r.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
DiscardOldestPolicy:当任务被拒绝添加时,会抛弃任务队列中最早进入的任务,再去添加这个新任务
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code DiscardOldestPolicy} for the given executor.
*/
public DiscardOldestPolicy() { }
/**
* Obtains and ignores the next task that the executor
* would otherwise execute, if one is immediately available,
* and then retries execution of task r, unless the executor
* is shut down, in which case task r is instead discarded.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
4、工作线程的回收
JVM中的垃圾回收机制就是将存在于JVM堆中Eden区中没有引用的对象视为垃圾,会在JVM的Minor GC中会被垃圾回收。线程池中线程的销毁同样依赖JVM自动的回收,线程池做的工作是根据当前线程池的状态维护一定数量的线程引用,防止这部分线程被JVM回收,当线程池决定哪些线程需要回收时,只需要将其引用消除即可。工作线程被创建出来后,就会不断地进行轮询,然后获取任务去执行,核心线程可以无限等待获取任务,非核心线程要限时获取任务。当工作线程无法获取到任务,也就是获取的任务为空时,循环会结束,工作线程会主动消除自身在线程池内的引用。
八、线程池在电商实际订单场景中的应用
在当今的电商平台中,为了最大程度化的利用多核CPU的性能,提高程序运行效率,所以通过线程池管理多线程成为了基础操作,接下里可以看下线程池在电商订单中的一个小的运用场景。
串行执行任务与并行执行任务通过下面两个图也能发现,串行执行任务效率远远低于并行执行任务。
通常电商平台如果每天下单量很大的情况下,那么它的取消订单量也会很大,所以这里就举一个取消订单中实际运用线程池的场景吧。用户取消订单后,会存在一个大量的短信push买卖家的消息,如果push短信的操作不是运用异步执行,那么QPS很高的时候,就会极大增加取消订单接口的RT,给用户的体验就会很差。
private void sendMsg(CancelOrderInfo cancelOrderInfo) {
try {
//异步发短信消息等
CompletableFuture.supplyAsync(
//具体的执行的方法
AfterOrderCancelSendMsg(cancelOrderInfo),
//获得线程池
CustomExecutorFactory.get(CustomExecutorServiceEnum.THREADPOOL_ARGS));
} catch (Throwable e) {
log.error("异步push异常 ", e);
}
}
看完觉得有收获可以点赞~~~