具体请参考原创:
《Java线程池实现原理及其在美团业务中的实践》
《Java 线程池及参数动态调节详解》
一、为何要使用线程池
降低资源消耗
线程的创建和销毁会造成一定的时间和空间上的消耗,线程池可以让我们重复利用已创建的线程。
提高响应速度
线程池已为我们创建好了线程,当任务到达时可以不需要等到线程创建就能立即执行。
提高线程的可管理性
线程是稀缺资源,不可能无限的创建,使用线程池可以进行统一分配、调优和监控。
提供更多更强大的功能
线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。
二、线程池核心参数及执行原理
1、核心参数
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
参数 | 说明 |
corePoolSize | 核心线程数量,线程池维护线程的最少数量 |
maximumPoolSize | 线程池维护线程的最大数量 |
keepAliveTime | 非核心线程的最长空闲时间,超过该时间的空闲线程会被销毁 |
unit | keepAliveTime的单位,有NANOSECONDS(纳秒)、MICROSECONDS(微秒)、MILLISECONDS(毫秒)、SECONDS(秒) |
workQueue | 任务缓冲队列(阻塞队列) |
threadFactory | 线程工厂,用于创建线程,一般用默认的即可 |
handle | 线程池对拒绝任务的处理策略 |
阻塞队列:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。
1.ArrayBlockingQueue:有界、数组结构、FIFO
2.LinkedBlockingQueue:有界、单链表结构、FIFO、默认长度Integer.MAX_VALUE
3.SynchronousQueue:不存储元素、每个put操作必须等待take操作,否则阻塞状态
4.PriorityBlockingQuene:无界、数组的平衡二叉堆、支持线程优先级排序、默认自然序、同优先级不能保证顺序
5.DelayQueue:无界、基于PriorityBlockingQuene、以时间作为比较基准的优先级队列,这个时间即延迟时间
ThreadPoolExecutor提供了四种拒绝策略:
1.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常(默认)
2.CallerRunsPolicy:由调用线程处理该任务( 常用)
3.DiscardPolicy:丢弃任务,但是不抛出异常。
4.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。
5.ThreadPoolExecutor的运行状态:
运行状态 | 状态描述 |
RUNNING | 能接受新提交的任务、并且也能处理阻塞队列中的任务 |
SHUTDOWN | 关闭状态,不再接受新提交的任务,但可以继续处理阻塞队列中已保存的任务 |
STOP | 不能接受新提及的任务,也不处理队列中的任务,会中断正在处理任务的线程 |
TIDYING | 所有的任务都已经终止了,workerCount(有效线程数)为0 |
TERMINATED | 在terminated()方法执行完后进入该状态 |
2、执行原理
三、线程池的创建和使用
1、线程池的创建方式
(1)通过Executors线程工厂类创建(不推荐)
1. Executors.newCachedThreadPool();
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0,
Integer.MAX_VALUE,
60L,
TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
该线程池存在的问题:允许的创建线程数量为 Integer.MAX_VALUE, 可能会创建大量的线程,从而导致 OOM。
2. Executors.newFixedThreadPool(int nThreads);
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads,
nThreads,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
该线程池存在的问题:允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
3. Executors.newSingleThreadExecutor();
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService (
new ThreadPoolExecutor(1,
1,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
该线程池存在的问题:允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
4. Executors.newScheduledThreadPool(int corePoolSize);
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize,
Integer.MAX_VALUE,
0,
NANOSECONDS,
new DelayedWorkQueue());
}
该线程池存在的问题:允许的创建线程数量为 Integer.MAX_VALUE, 可能会创建大量的线程,从而导致 OOM。
(2)通过new ThreadPoolExecutor自定义创建(推荐)
ThreadPoolExecutor pool = new ThreadPoolExecutor(5,
20,
60,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(200));
2、线程池使用规范(阿里巴巴)
创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。
线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。 自行创建线程,有可能造成系统创建大量同类线程而导致消耗完内存或者 “过度切换”的问题。
线程池不允许使用 Executors工厂类 去创建,而是通过new ThreadPoolExecutor 的方式,这样 的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
3、SpringBoot项目中使用线程池
(1)配置线程池并开启异步任务
@Configuration
@EnableAsync // 开启异步任务支持
public class ExecutorConfig {
// 声明线程池
@Bean("taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(2000);
executor.setThreadNamePrefix("taskExecutor-");
executor.setRejectedExecutionHandler(new
ThreadPoolExecutor.CallerRunsPolicy());
//执行初始化
executor.initialize();
return executor;
}
}
(2)在@Async中使用自定义线程池
@Service
public class TaskService {
// @Async声明方法为异步方法并自定使用自定义线程池
@Async("taskExecutor")
public void task1() {
// 具体业务
}
}
(3)@Async失效(本质是代理没有生效)
异步方法使用了static修饰
异步方法所在类没有使用@Component或@Service注解进行注释,导致spring无法扫描到异步类
异步方法类应使用@Autowired或@Resource等注解自动注入到使用类中,不能自己手动new对象
没有在启动类或配置类中增加@EnableAsync注解启动异步任务支持
异步方法不能由本类内其他方法调用,必须是外部使用者调用,如果内部方法调用会出现代理绕过的问题,会变成同步操作
(需要继续补充)
四、合理配置线程池参数&线程池参数动态配置&线程池监控
具体参考:
《Java线程池实现原理及其在美团业务中的实践》
《Java 线程池及参数动态调节详解》
1、合理配置线程池参数(并没用通用的计算方式)
业界的一些线程池参数配置方案:
2、线程池参数动态配置&线程池监控 (美团业务中的方案)
线程池参数动态化前后的参数修改流程对比:
线程池参数动态化整体架构:
线程池参数动态化功能架构: