ThreadPool线程池

线程资源通过线程池提供

线程池可以减少创建、销毁线程的开销,解决资源不足问题。
如果手动创建线程,容易造成系统存在大量同类线程而导致内存耗尽、过度切换问题。

不使用Executors

Executors.newFixedThreadPool() 固定大小线程池
Executors.newSingleThreadExecutor() 单线程池
Executors.newCachedThreadPool() 动态线程池

Executors底层仍然使用new来创建线程,容易造成OOM。

自己调用ThreadPoolExecutor

推荐的方法使,直接自己调用new ThreadPoolExecutor来创建线程池,设置自己的参数

public ThreadPoolExecutor(
@Range(from = 0, to = Integer.MAX_VALUE) int corePoolSize,
@Range(from = 1, to = Integer.MAX_VALUE) int maximumPoolSize,
@Range(from = 0, to = Long.MAX_VALUE) long keepAliveTime,
@NotNull TimeUnit unit,
@NotNull BlockingQueue<Runnable> workQueue,
@NotNull ThreadFactory threadFactory,
@NotNull RejectedExecutionHandler handler
) { ... }

// Example
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
5,
10,
0L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(10), // 10为容量
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);

corePoolSize 核心线程数量。
maximumPoolSize 最大线程数量(核心 + 临时)。只有核心线程满了,而且阻塞队列也满了,才会创建临时线程。
keepAliveTime 临时线程的空闲存活时间。
threadFactory 线程工厂,即以什么方式创建线程。
handler 核心线程、阻塞队列、临时线程都满了,触发拒绝策略。

Executors的问题在于,它指定的阻塞队列大小是Integer.MAX_VALUE,会导致内存溢出;而Executors.newCachedThreadPool()更是指定了maximumPoolSizeInteger.MAX_VALUE

拒绝策略,如何保证线程不丢

使用直接拒绝AbortPolicy策略,线程会丢失。此时可以使用

  • CallerRunsPolicy 直接在主线程同步执行(可能会阻塞主线程)
  • DiscardOldestPolicy 将队列头部删除,新线程尾部入队(保证新任务优先级)
  • DiscardPolicy 可以自己拓展,例如将任务放到redis、rocketmq中

ThreadPool工作流程

当前线程数没有达到corePoolSize之前,每个新任务都会触发创建新线程;
达到以后,才会放到阻塞队列里,等待线程任务执行完成,分发任务给核心线程。
阻塞队列满了,才会创建临时线程执行任务。

ThreadPool如何初始化?

供参考的经验值:

  • CPU密集任务(如数据统计、排序):
    核心线程 = 最大线程 = 核心数 + 1
    减少上下文切换开销
  • IO密集任务:
    核心线程 = 核心数 * 2;最大线程 = 核心数 * 4

动态线程池