线程池工作流程
使用线程池的好处?
- 降低资源消耗。通过重复利用已创建的线程降低在频繁创建和销毁线程上所带来的性能损耗。
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。但是,要做到合理利用线程池,必须对其实现原理了如指掌。
概念
CorePoolSize: 核心线程数。
MaximumPoolSize: 最大线程数。
WorkQueue: 工作队列。
流程
- 创建线程池后,等待提交过来的任务请求。
- 调用
execute()
方法添加一个请求任务。
- 如果正在运行的线程数量小于
corePoolSize
,那么马上创建线程执行这个任务。 - 如果正在运行的线程数量大于或等于
corePoolSize
,那么把这个任务放入工作队列。 - 如果这个时候队列满了,且正在运行的线程数量还小于
maximumPoolSize
,那么创建非核心线程立即运行这个任务。 - 如果队列满了,且正在运行的线程数大于或等于
maximumPoolSize
,那么启动饱和拒绝策略来执。
- 如果正在运行的线程数量小于
- 当一个线程完成任务时,它会从队列中取下一个任务来执行。
- 当一个线程无事可做超过一定时间
KeepAliveTime
时,线程池会判断:如果当前运行线程数大于corePoolSize
,这个线程被停掉。
拒绝策略
AbortPolicy(默认):直接抛出RejectedExecuteException异常,阻止系统正常运行。
CallerRunsPolicy:“调用者运行”的一种机制,既不会抛弃任务,也不会抛出异常,而是将任务回退到调用者。
DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列。
DiscardPolicy:直接丢弃,不做任何处理也不抛出异常。
自定义线程池
为什么要自定义线程池?
因为Executors
工具创建的线程池阻塞队列容量为Integer.MAX_VALUE
。可能堆积大量请求,导致OOM。
ExecutorService threadPool = new ThreadPoolExecutor(
2,
5,
60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(5),
Executors.defaultThreadFactory(),
new ThreadPoolThread.CallerRunsPolicy()
);
获取 CPU 核数 n:
Runtime.getRuntime().availableProcessors();
- CPU 密集型,应尽量减少线程数量:
corePoolSize = n+1 - I/O 密集型,大部分线程阻塞,线程应尽可能配多:
阻塞系数=0.8~0.9
corePoolSize = n*(1-阻塞系数)
定位死锁
- 查看 java 进程,定位进程号
jps -l
- 根据进程号打印堆信息
jstack xxx