Java线程池的创建方法

本文最后更新于:2022年12月19日 晚上

1、自动创建(不推荐)

在Java8以后,线程池的创建方法有5种,这里介绍3种常用的方法。

1
2
3
ExecutorService threadPool = Executors.newFixedThreadPool(5);
ExecutorService threadPool = Executors.newSingleThreadExecutor();
ExecutorService threadPool = Executors.newCachedThreadPool();

第一种是创建一个定容的线程池;

第二种是创建一个只有一个线程容量的线程池;

第三种是创建一个带缓存可扩容的线程池。

但是在使用时,阿里的代码检测工具会提示手动建立线程池。看一下源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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>());
}

三种方法底层其实都是使用了new ThreadPoolExecutor的方式创建线程池,第三种的最大线程容量甚至达到了Integer.MAX_VALUE,如果允许线程池无限扩容的话,会创建非常多的线程。而前两种的主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。

所以如何手动创建线程池是我们需要学习的。

手动创建线程池

我们可以通过new ThreadPoolExecutor的方式手动创建线程池,首先我们要了解构造方法的七个参数的具体含义。

直接看代码:

1
2
3
4
5
6
7
8
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)

corePoolSize:线程池中的常驻核心数线程;

maximumPoolSize:线程池能够容纳的同时执行的最大线程数;

keepAliveTime:多余的线程的存活时间;

unit:对keepAliveTime 的时间单位;

workQueue:任务队列,被提交但尚未被执行的任务;

threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程;

handler:拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数。

1
2
3
4
5
6
7
ExecutorService threadPool = new ThreadPoolExecutor(2,
5,
1L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());

拒绝策略

定义:等待队列也已经排满了,再也塞不下新的任务了同时,线程池的max也到达了,无法接续为新任务服务这时我们需要拒绝策略机制合理的处理这个问题。

JDK内置的拒绝策略:

  • AbortPolicy(默认): 直接抛出RejectedException异常,阻止系统正常运行;

  • CallerRunPolicy: 不会抛异常,也不会抛弃任务,而是会将某些任务回退到调用它的线程。

    如:main调用了thread-pool-0计算1+1,而此时线程池已满,启动拒绝策略,会将这个任务返还给main线程,让main线程执行计算;

  • DiscardOldestPolicy: 抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交;

  • DiscardPolicy: 直接抛弃任务,不予任何处理,也不抛出异常。如果允许任务丢失,这是最好的方法。

合理配置线程池的容量

如何合理配置线程池?

CPU密集型

CPU密集的意思是该任务需要大量的计算,而没有阻塞,CPU一直进行计算;

一般公式:CPU核数+1个线程的线程池。

I/O密集型

IO密集型不需要大量计算,而需要进行大量IO操作阻塞。

IO密集型,大多线程都会阻塞,所以需要尽可能多的线程:

  • CPU核数 / (1 - 阻塞系数) 阻塞系数在0.8-0.9之间
  • CPU核数 * 2

两种公式视场合使用。