Java 中创建线程池的五种方法及比较
转(部分调整):JAVA 中创建线程池的五种方法及比较
在 Java 中创建线程池主要有两种方式:一种是通过Executors工厂类提供的方法,该类提供了四种不同类型的线程池供开发者快速使用;另一种是直接通过ThreadPoolExecutor类进行自定义创建。
一、通过Executors工厂类创建线程池
Executors类提供了一系列静态工厂方法,简化了常见线程池的创建过程。
1. newCachedThreadPool
创建一个可缓存的线程池。当任务量增加时,若现有线程不足,则会新建线程;当线程空闲一段时间后,若线程数超过处理所需,则会被回收。
13540619
代码示例:
private static void createCachedThreadPool() {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int index = i;
executorService.execute(() -> {
// 获取线程名称,默认格式:pool-1-thread-1
System.out.println(DateUtil.now() + " " + Thread.currentThread().getName() + " " + index);
// 等待 2 秒
sleep(2000);
});
}
}
执行效果:
![]() |
由于初始线程池中没有线程,且当线程不足时会不断新建线程,因此执行过程中会看到不同的线程名称。
2. newFixedThreadPool
创建一个固定大小的线程池,用于控制并发执行的线程数量。超出线程池容量的任务会在队列中等待执行。
代码示例:
private static void createFixedThreadPool() {
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
final int index = i;
executorService.execute(() -> {
// 获取线程名称,默认格式:pool-1-thread-1
System.out.println(DateUtil.now() + " " + Thread.currentThread().getName() + " " + index);
// 等待 2 秒
sleep(2000);
});
}
}
执行效果:
![]() |
由于线程池大小固定为 3,因此只会看到 3 个不同的线程名称。当线程不足时,任务会进入队列等待线程空闲,导致日志输出间隔为 2 秒。
3. newScheduledThreadPool
创建一个支持定时及周期性执行任务的线程池。
代码示例:
private static void createScheduledThreadPool() {
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
System.out.println(DateUtil.now() + " 提交任务");
for (int i = 0; i < 10; i++) {
final int index = i;
executorService.schedule(() -> {
// 获取线程名称,默认格式:pool-1-thread-1
System.out.println(DateUtil.now() + " " + Thread.currentThread().getName() + " " + index);
// 等待 2 秒
sleep(2000);
}, 3, TimeUnit.SECONDS);
}
}
执行效果:
![]() |
由于设置了 3 秒的延迟,任务会在提交 3 秒后开始执行。此外,由于核心线程数为 3,当线程不足时,任务会进入队列等待空闲线程,导致日志输出间隔为 2 秒。
注意:此处使用的是ScheduledExecutorService类的schedule()方法,而非ExecutorService类的execute()方法。
4. newSingleThreadExecutor
创建一个单线程的线程池,可以确保所有任务按照指定的顺序(如 FIFO、LIFO 或优先级)依次执行。
代码示例:
private static void createSingleThreadPool() {
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
executorService.execute(() -> {
// 获取线程名称,默认格式:pool-1-thread-1
System.out.println(DateUtil.now() + " " + Thread.currentThread().getName() + " " + index);
// 等待 2 秒
sleep(2000);
});
}
}
执行效果:
![]() |
由于线程池中只有一个线程,所有任务都由该线程执行,因此线程名称相同,并且每隔 2 秒按顺序输出。
二、通过ThreadPoolExecutor自定义创建线程池
ThreadPoolExecutor类提供了多种构造方法,允许开发者根据具体需求自定义线程池。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
// 省略...
}
1. 核心参数详解
ThreadPoolExecutor的构造方法包含以下七个核心参数:
-
corePoolSize:核心线程数,线程池中始终保持存活的线程数量。 -
maximumPoolSize:最大线程数,线程池允许创建的最大线程数量。 -
keepAliveTime:线程空闲时间,当线程池中的线程数量超过corePoolSize时,这些多余的空闲线程在终止前会等待新任务的最长时间。 -
unit:keepAliveTime参数的时间单位。 -
workQueue:一个阻塞队列,用于存储等待执行的任务。所有队列均是线程安全的,有 7 种可选类型。参数 描述 ArrayBlockingQueue一个由数组结构组成的有界阻塞队列。 LinkedBlockingQueue一个由链表结构组成的有界(或无界)阻塞队列。 SynchronousQueue一个不存储元素的阻塞队列,即直接将任务提交给线程而不进行保持。 PriorityBlockingQueue一个支持优先级排序的无界阻塞队列。 DelayQueue一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能从中提取元素。 LinkedTransferQueue一个由链表结构组成的无界阻塞队列。与 SynchronousQueue类似,还含有非阻塞方法。LinkedBlockingDeque一个由链表结构组成的双向阻塞队列。 其中,
LinkedBlockingQueue和SynchronousQueue较为常用。线程池的任务排队策略与所选的BlockingQueue类型密切相关。 -
threadFactory:线程工厂,用于创建新线程。默认情况下,它创建的是具有正常优先级、非守护状态的线程。 -
handler:拒绝策略,当线程池无法处理新提交的任务时所采取的策略。有 4 种可选策略,默认为ThreadPoolExecutor.AbortPolicy。参数 描述 AbortPolicy直接拒绝任务并抛出 RejectedExecutionException异常。CallerRunsPolicy在调用 execute()方法的线程中直接运行该任务。DiscardOldestPolicy抛弃任务队列中最旧(头部)的一个任务,并尝试重新提交当前任务。 DiscardPolicy直接抛弃当前任务,不作任何处理。
2. 线程池执行规则
ThreadPoolExecutor的执行规则如下:
- 当线程池中的线程数量小于
corePoolSize时,即使有空闲线程,也会创建新线程来执行任务。 - 当线程池中的线程数量达到
corePoolSize,且任务队列未满时,新提交的任务会被放入任务队列等待。 - 当线程池中的线程数量达到
corePoolSize,且任务队列已满时:- 若线程数小于
maximumPoolSize,则创建新线程执行任务。 - 若线程数已达到
maximumPoolSize,则执行拒绝策略,抛出异常或根据策略处理任务。
- 若线程数小于
代码示例:
private static void createThreadPool() {
ExecutorService executorService = new ThreadPoolExecutor(2, 10,
1, TimeUnit.MINUTES, new ArrayBlockingQueue<>(5, true),
Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < 10; i++) {
final int index = i;
executorService.execute(() -> {
// 获取线程名称,默认格式:pool-1-thread-1
System.out.println(DateUtil.now() + " " + Thread.currentThread().getName() + " " + index);
// 等待 2 秒
sleep(2000);
});
}
}
执行效果:
![]() |
由于核心线程数为 2,任务队列大小为 5,线程存活时间为 1 分钟,执行流程如下:
- 当任务 0 和任务 1 到来时,会创建 2 个核心线程执行它们。
- 任务 2 至任务 6 到来时,由于核心线程已满,且队列未满,这些任务会进入任务队列等待。
- 任务 7 至任务 9 到来时,由于核心线程已满,且任务队列也已满,因此会创建额外的 3 个线程来执行这些任务。
- 值得注意的是,任务 7-9 可能会比任务 2-6(队列中的任务)更早执行,这取决于任务的执行速度和新线程创建的速度。
- 由于每个任务仅需 2 秒,而线程存活时间为 1 分钟,线程会被复用,最终总共创建了 5 个线程(2 个核心线程 + 3 个非核心线程)。
三、Executors工厂类方法的底层实现与使用建议
虽然我们讨论了五种线程池的创建方式,但本质上可以归结为两类。Executors工厂类提供的四种便捷方法,其底层均是通过ThreadPoolExecutor类来实现的。换句话说,Executors通过预设ThreadPoolExecutor的不同参数组合,为特定场景提供了封装好的线程池。下面我们将通过源码分析来揭示这一点:
1. newCachedThreadPool的实现
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
由于使用了SynchronousQueue,该队列不存储元素,任务会直接提交给线程执行,这相当于一个队列大小为 0 的队列。同时,最大线程数设置为Integer.MAX_VALUE,这意味着当线程不足时,会无限创建新线程。当线程空闲 60 秒后,会被回收,从而实现了可缓存线程池的特性。
2. newFixedThreadPool的实现
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
由于corePoolSize和maximumPoolSize均设置为nThreads,线程池的线程数量是固定的。同时,使用了无界队列LinkedBlockingQueue,这意味着所有多余的任务都会进入队列排队,从而实现了一个固定大小、并发数量可控的线程池。
3. newScheduledThreadPool的实现
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
其核心在于使用了DelayedWorkQueue,这是一个延迟队列,只有当元素的延迟期满后才能从中提取。这使得线程池能够实现定时执行任务的功能。至于周期性执行,则是通过ScheduledExecutorService类中scheduleAtFixedRate等方法的上层封装来实现的。
4. newSingleThreadExecutor的实现
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
由于corePoolSize和maximumPoolSize均设置为 1,线程池始终只有一个工作线程。同时,使用了无界队列LinkedBlockingQueue,这意味着所有多余的任务都会进入队列排队,从而实现了一个单线程按指定顺序执行的线程池。
阿里巴巴《Java 开发手册》建议
尽管Executors工厂类提供的封装方法看似简化了线程池的使用,但根据《阿里巴巴 Java 开发手册》的强制规范,明确不建议直接使用Executors类创建线程池:
【强制】线程池不允许使用
Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
Executors返回的线程池对象的弊端如下:
FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
CachedThreadPool和ScheduledThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
通过上述源码分析,我们可以验证此规范的合理性。因此,建议开发者直接使用ThreadPoolExecutor类来创建线程池,并根据具体应用场景合理配置各项参数,以规避潜在的资源耗尽风险。





浙公网安备 33010602011771号