线程池使用详解
一、线程池使用背景:
创建线程可以通过继承Thread类或实现Runnable接口,但会带来线程创建和销毁的资源占用,线程切换上下文问题,同时创建过多的线程可能会导致系统资源耗尽的风险,固不推荐使用此种方式来执行多线程,可以采用创建线程池来执行多线程,方便对线程任务的管理。
二、线程池使用场景
- 加快请求响应
- 在助贷系统中,渠道方推送流量到本系统进行借贷申请,由于内部需要走很长的业务审核流程及三方平台的接口交互,会导致整个借贷申请时间过长,固在创建借贷申请任务后,后面的业务流程通过异步的方式处理,此时返回响应结果给渠道方。
- 渠道方可以通过轮询查询本系统申请结果接口或本系统以回调渠道方接口来完成此次最终借贷申请。
- 提供系统吞吐量
- 如果渠道方推过来的流量较多,如果采用同步处理,系统吞吐量极低,固通过线程池异步任务来处理提交系统吞吐量。
三、线程池有两种创建方式
- 自动创建(Executors),提供静态方法创建线程池 -- 不推荐使用
Executors提供了各种线程池类型创建的静态方法,如常见的newFixedThreadPool、newSingleThreadExecutor、newCachedThreadPool、newSingleThreadScheduledExecutor。
但不提倡使用该种方式创建线程池,在阿里巴巴JAVA开发手册,对于线程池创建要求:
- 手动创建(ThreadPoolExecutor),提供构造方法创建线程池 -- 推荐使用
主要看参数最全的构造方法,其他构造方法最终还是会调用该改造方法
corePoolSize:核心线程数,线程池初始化时默认是没有线程的,当任务来临时才开始创建线程去执行任务
maximumPoolSize:最大线程数,在核心线程数已满,且队列已满时,如果池子里的工作线程数小于maximumPoolSize,则会创建非核心线程执行任务
keepAliveTime:非核心线程数的空闲时间超过keepAliveTime就会被自动终止回收掉,但在corePoolSize=maximumPoolSize时,该值无效,因为不存在非核心线程
unit:keepAliveTime的时间单位
workQueue:用于保存线程任务的队列,主要分为无界、有界、同步移交等队列,当池子里的工作线程数大于corePoolSize,就会将新进来的线程任务放入队列中
- ArrayBlockingQueue(有界队列):队列长度有限,当队列满了就需要创建非核心线程执行任务,如果最大线程数已满,则执行拒绝策略
- LinkedBlockingQueue(无界队列):队列长度无限,当任务处理速度跟不上任务创建速度,可能会导致内存占用过多或OOM
- SynchronousQueue(同步移交队列):队列不作为任务的缓冲处理,队列长度为0
threadFactory:
- 创建线程的工厂接口,默认使用Executors.defaultThreadFactory()
- 另外可以实现ThreadFactory接口,自定义线程工厂
handler:线程池无法继续接收任务时(workQueue已满和maximumPoolSize已满)的拒绝策略
- AbortPolicy:默认拒绝策略,中断抛出RejectedExecutionException异常
- CallerRunsPolicy:让提交任务的主线程来执行任务
- DiscardOldestPolicy:丢弃在队列中存在时间最久的任务,重复执行
- DiscardPolicy:丢弃任务,不进行任何通知
- 另外可以实现RejectedExecutionHandler接口,自定义拒绝策略
四、线程池创建流程图
1.提交任务
2.判断核心线程数corePoolSize是否已满,如果未满则创建线程来执行任务;如果已满则走第3步
3.判断任务队列workQueue是否已满,如果未满则将新任务放入到workQueue;如果已满则走第4步
4.判断最大线程数maximumPoolSize是否已满,如果未满则创建线程来执行任务;如果已满则走第5步
5.拒绝策略处理当前进来的任务
五、关闭线程池
- shutdownNow():立即关闭线程池,正在执行中的任务和队列中的任务都会被中断,同时返回被中断的队列中的任务列表
- shutdown():关闭线程池,正在执行中的任务和队列中的任务都能执行完成,后续进来的新任务会被执行拒绝策略
- isTerminated():当正在执行的任务和队列中的任务全部都执行完时返回true
六、手动创建线程池
// 指定拒绝策略为CallerRunsPolicy private static ExecutorService executor = new ThreadPoolExecutor(10, 10, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue(20), new ThreadPoolExecutor.CallerRunsPolicy());
也可以使用guava包中的ThreadFactoryBuilder工厂类来构造线程池
// 可以指定线程组名称,帮助后期定位问题及线程池使用分析 ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("thread-pool-d%").build(); // 手动创建线程池 private static ExecutorService executorService = new ThreadPoolExecutor(10, 10, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(20), threadFactory, new ThreadPoolExecutor.AbortPolicy());
七、Spring Boot中使用线程池
个人习惯将不同的线程池分别配置,并进行一定的封装处理。例如"默认/订单处理/发送短信"等线程池
1.线程池配置
- 线程池属性类定义
package com.coolw.task.threadpool; import lombok.Data; /** * @Description 线程池配置属性 * @Date 2021/5/21 15:51 * @Author coolw */ @Data public class ThreadProperties { /** 核心线程数 */ private int corePoolSize; /** 最大线程数 */ private int maxPoolSize; /** 线程存储队列数 */ private int queueSize; /** 允许非核心线程的空闲时间 */ private long keepAlive; /** 线程名称前缀 */ private String namePrefix; }
- application.properties线程池属性配置
# 默认线程池配置 default.pool.corePoolSize=2 default.pool.maxPoolSize=5 default.pool.queueSize=10 default.pool.keepAlive=60 default.pool.namePrefix=default-task # 订单线程池配置 order.pool.corePoolSize=10 order.pool.maxPoolSize=20 order.pool.queueSize=100 order.pool.keepAlive=60 order.pool.namePrefix=oder-task # 短信线程池配置 sms.pool.corePoolSize=5 sms.pool.maxPoolSize=10 sms.pool.queueSize=50 sms.pool.keepAlive=60 sms.pool.namePrefix=sms-task
- 线程池配置类
package com.coolw.task.threadpool; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * @Description 线程池配置 * @Date 2021/5/21 15:51 * @Author coolw */ @Configuration public class ThreadPoolConfig { @ConfigurationProperties("default.pool") @Bean("defaultThreadProperties") public ThreadProperties defaultThreadProperties() { return new ThreadProperties(); } @ConfigurationProperties("order.pool") @Bean("orderThreadProperties") public ThreadProperties orderThreadProperties() { return new ThreadProperties(); } @ConfigurationProperties("sms.pool") @Bean("smsThreadProperties") public ThreadProperties smsThreadProperties() { return new ThreadProperties(); } @Bean("defaultExecutorService") public ExecutorService defaultExecutorService(@Qualifier("defaultThreadProperties") ThreadProperties properties) { return new ThreadPoolExecutor(properties.getCorePoolSize(), properties.getMaxPoolSize() , properties.getKeepAlive(), TimeUnit.SECONDS, new ArrayBlockingQueue<>(properties.getQueueSize()) , new MyThreadFactory(properties.getNamePrefix()) ,new ThreadPoolExecutor.CallerRunsPolicy()); } @Bean("orderExecutorService") public ExecutorService orderExecutorService(@Qualifier("orderThreadProperties") ThreadProperties properties) { return new ThreadPoolExecutor(properties.getCorePoolSize(), properties.getMaxPoolSize() , properties.getKeepAlive(), TimeUnit.SECONDS, new ArrayBlockingQueue<>(properties.getQueueSize()) , new MyThreadFactory(properties.getNamePrefix()) ,new ThreadPoolExecutor.CallerRunsPolicy()); } @Bean("smsExecutorService") public ExecutorService smsExecutorService(@Qualifier("smsThreadProperties") ThreadProperties properties) { return new ThreadPoolExecutor(properties.getCorePoolSize(), properties.getMaxPoolSize() , properties.getKeepAlive(), TimeUnit.SECONDS, new ArrayBlockingQueue<>(properties.getQueueSize()) , new MyThreadFactory(properties.getNamePrefix()) ,new ThreadPoolExecutor.CallerRunsPolicy()); } }
- 自定义线程工厂
package com.coolw.task.threadpool; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; /** * @Description 自定义线程工厂 * @Date 2021/5/21 16:12 * @Author coolw */ public class MyThreadFactory implements ThreadFactory { private final ThreadGroup group; private final AtomicInteger threadNumber = new AtomicInteger(1); private final String namePrefix; MyThreadFactory(String namePrefix) { SecurityManager s = System.getSecurityManager(); group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); this.namePrefix = namePrefix + "-thread-"; } @Override public Thread newThread(Runnable r) { Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0); // 设置为守护线程 if (t.isDaemon()) { t.setDaemon(true); } // 设置优先级 if (t.getPriority() != Thread.NORM_PRIORITY) { t.setPriority(Thread.NORM_PRIORITY); } return t; } }
2.代码示例
- service接口层
/** * @Description 订单渠道接口 * @Date 2021/5/21 16:21 * @Author coolw */ public interface OrderChannelService { void createOrder(); }
/** * @Description 订单渠道接口 * @Date 2021/5/21 16:21 * @Author coolw */ public interface SmsService { void sendSms(String mobileNo); }
- serviceImpl接口实现层(淘宝、拼多多、每日优鲜)等
package com.coolw.task.service.impl; import com.coolw.task.service.OrderChannelService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; /** * @Description 淘宝订单渠道 * @Date 2021/5/21 16:21 * @Author coolw */ @Slf4j @Service("tbOrderChannelService") public class TBOrderChannelServiceImpl implements OrderChannelService { @Override public void createOrder() { log.info("淘宝创建订单成功......"); } }
package com.coolw.task.service.impl; import com.coolw.task.service.SmsService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; /** * @Description TODO * @Date 2021/5/21 16:21 * @Author coolw */ @Slf4j @Service("smsService") public class SmsServiceImpl implements SmsService { @Override public void sendSms(String mobileNo) { log.info("手机号[{}]发生短信成功......", mobileNo); } }
- controller层
package com.coolw.task.controller; import com.coolw.task.holder.SpringContextHolder; import com.coolw.task.service.OrderChannelService; import com.coolw.task.service.SmsService; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.util.concurrent.ExecutorService; @RestController public class TestController { @Resource private ExecutorService orderExecutorService; @Resource private ExecutorService smsExecutorService; @Resource private ExecutorService defaultExecutorService; @Resource private SmsService smsService; @PostMapping("/order/create/{channelCode}") public String createOrder(@PathVariable String channelCode) { OrderChannelService orderChannelService = SpringContextHolder.getBean(channelCode.toLowerCase() + "OrderChannelService"); orderExecutorService.execute(orderChannelService::createOrder); //defaultExecutorService.execute(orderChannelService::createOrder); return "ok"; } @PostMapping("/sms/send/{mobileNo}") public String smsSend(@PathVariable String mobileNo) { smsExecutorService.execute(() -> smsService.sendSms(mobileNo)); return "ok"; } }
- 测试结果
十、线程池的优化
todo......
十一、线程池工作原理
todo......