为什么使用线程池
池化思想,线程池的主要目的是减少在创建和销毁线程时所花费的开销和资源,提高程序性能、提高资源利用率,同时也提供了对并发执行任务的更好管理,例如控制线程数量。
优势:线程复用、资源控制、方便管理。
线程池七大参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler);
corePoolSize:核心线程数,即使线程处于空闲状态也不会被回收
maximumPoolSize:最大线程数,在核心线程和队列均满且线程数量未达到最大线程数时,创建新的线程处理任务。新建的线程在空闲超过设定的时间后,会销毁线程。
keepAliveTime和TimeUnit:空闲的非核心线程的存活时间及时间单位
BlockingQueue:阻塞队列,当核心线程全在执行任务时,新来的任务保存到阻塞队列中等待执行
ThreadFactory:创建线程的工厂,使用默认的工厂即可,也可以自定义工厂,通常设置自定义的线程名称,查看日志排查问题时来达到区分线程的效果。
RejectedExecutionHandler:拒绝策略,在线程池已经创建最大的线程数量且队列满时,新提交的任务的拒绝策略。
核心线程数和最大线程数怎么设置?
根据服务器的配置和当前服务类型来选择,可参考的配置如下:
- CPU密集型服务(使用CPU资源较大):核心线程数配置为服务器处理器的核心数,最大线程数可以在此基础上稍微增加
- IO密集型服务(IO过程中线程被挂起,其他线程可以继续执行,消耗CPU资源较少):核心线程数配置为服务器处理器的核心数的两倍,最大线程数在此基础上稍微增加
例如4核16G的服务器,当前服务两个线程池可以这样配置:
- 通用线程池:3核心,6最大线程;定时任务线程池:1核心,4最大线程。
生产环境配置过程中,根据压测情况来配置合适的线程数。
非核心线程存活时间如何设置
根据服务来配置,可参考如下:
- 短期高并发场景:设置较短的存活时间30s或1min,尽快释放资源,减少不必要的线程占用。
- 长期稳定的负载:选择较长的时间2min或5min,减少线程的频繁创建和销毁,提高资源利用率。
生产环境配置过程中,根据压测情况来配置合适的存活时间。
选择哪种阻塞队列?
最常用的是 ArrayBlockingQueue 和 LinkedBlockingQueue:
- ArrayBlockingQueue 适用于需要严格控制队列容量的场景,使用数组实现,使用一个互斥锁来保护整个队列。
- LinkedBlockingQueue 适用于需要高性能队列的场景,使用链表实现,使用两个锁来分别保护 put 和 take操作,减少了锁的竞争,提高了并发性能,并且可以通过指定容量来避免内存溢出。
- 如果应用场景需要支持优先级排序,那么 PriorityBlockingQueue 也是一个很好的选择。如果需要创建固定大小的线程池,并且希望线程间直接传递任务,那么 SynchronousQueue 也非常有用。
线程工厂
可以自定义线程工厂,设置新建线程的属性和状态,比如修改线程前缀名称,设置是否线程为守护线程。一般执行定时任务后台任务之类的可以设置为守护进程,执行业务核心逻辑的线程默认非守护线程。
拒绝策略有哪些?该怎么选择?
- AbortPolicy:拒绝当前任务并抛出异常
- CallerRunsPolicy:拒绝当前任务,将任务交个调用线程自己执行
- DiscardPolicy:拒绝当前任务
- DiscardOldestPolicy:将队列中第一个任务抛弃,将当前任务加到队列尾部
分情况来选择:
如果任务可以丢弃,选择DiscardPolicy或者DiscardOldestPolicy
如果任务不能丢弃,且调用线程执行任务在大多数情况下系统可以负担,选用CallerRunsPolicy
如果任务不能丢弃,也可以选用AbortPolicy,在抛出异常后执行补偿逻辑(存入数据库或其他操作)
也可以自定义拒绝策略来实现自定义的需求
线程池的执行流程
JUC中提供了几种线程池实现,他们都有什么区别?
线程池的状态有哪几种?他们之间如何转换的?
// runState is stored in the high-order bits
运行状态保存在高(3)位里,COUNT_BITS = Integer. SIZE - 3 == 29
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;