maximumPoolSize 最大线程数
keepAliveTime 存活时间
unit 时间单位
workQueue 存放待执行任务的队列
threadFactory 创建线程的工厂。创建线程或线程池时指定有意义的线程名称,方便出错时回溯。
handler 拒绝策略
项目中,我们会使用简单的线程隔离的方案,每个业务使用一个线程池。
这个是要根据具体的业务来决定的,如果是「CPU 密集型任务」:比如像加解密,压缩、计算等一系列需要大量耗费 CPU 资源的任务,大部分场景下都是纯 CPU 计算。尽量使用较小的线程池,一般为 CPU 核心数+1。因为 CPU 密集型任务使得 CPU 使用率很高,若开过多的线程数,会造成 CPU 过度切换。如果是「IO 密集型任务」:比如像 MySQL 数据库、文件的读写、网络通信等任务,这类任务不会特别消耗 CPU 资源,但是 IO 操作比较耗时,会占用比较多时间。可以使用稍大的线程池,一般为 2 * CPU 核心数。IO 密集型任务 CPU 使用率并不高,因此可以让 CPU 在等待 IO 的时候有其他线程去处理别的任务,充分利用 CPU 时间。
CPU 密集型:核心数 + 1(即使当计算(CPU)密集型的线程偶尔由于页缺失故障或者其他原因而暂停时,这个“额外”的线程也能确保 CPU 的时钟周期不会被浪费。)
IO 密集型:
FixedThreadPool 和 SingleThreadPool 的 workQueue 长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
CachedThreadPool 的 maximumPoolSize 数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
线程池被创建后如果没有任务过来,里面是不会有线程的。如果需要预热的话可以调用下面的两个方法:prestartAllCoreThreads() 和 prestartCoreThread()。
// Starts all core threads.
public int prestartAllCoreThreads() {
int n = 0;
while (addWorker(null, true))
++n;
return n;
}
// Starts a core thread.
public boolean prestartCoreThread() {
return workerCountOf(ctl.get()) < corePoolSize &&
addWorker(null, true);
}
submit() 和 execute() 都可以往线程池中提交任务,区别是使用 execute() 执行任务无法获取到任务执行的返回值,而使用 submit()方法, 可以使用 Future 来获取任务执行的返回值。
shutdownNow() 和 shutdown() 都是用来终止线程池的,它们的区别是,使用 shutdown() 程序不会报错,也不会立即终止线程,它会等待线程池中的缓存任务执行完之后再退出,执行了 shutdown() 之后就不能给线程池添加新任务了;shutdownNow() 会试图立马停止任务,线程中的任务不会再执行,也无法添加新的任务。
这个是不一定的,因为线程池会调用线程的 interrupt() 来中断线程的执行,但是这个方法不会打断正在运行的线程,只对正在阻塞等待的线程生效,一旦线程执行的任务类似于一个死循环,那么任务永远不会执行完,那么线程永远都不会退出。
因为线程一旦任务执行完之后,如果想让线程不退出,只能阻塞或者自旋来保证线程不会退出,阻塞会让 cpu 资源,但是自旋不会,所以为了防止线程退出和减少 cpu 的消耗,选择使用阻塞队列来保证线程不会退出。
Java 线程池实现原理及其在美团业务中的实践