# Executors
TIP
逻辑上,Executors 是 ThreadPoolExecutor 的工具类。
Executors 可以创建以下 6 种线程池。
# | 方法 | 说明 |
---|---|---|
1 | FixedThreadPool | 创建一个数量固定的线程池,超出的任务会在队列中等待空闲的线程,可用于控制程序的最大并发数。 |
2 | CachedThreadPool | 短时间内处理大量工作的线程池,会根据任务数量产生对应的线程,并试图缓存线程以便重复使用,如果限制 60 秒没被使用,则会被移除缓存。 |
3 | SingleThreadExecutor | 创建一个单线程线程池。 |
4 | ScheduledThreadPool | 创建一个数量固定的线程池,支持执行定时性或周期性任务。 |
5 | SingleThreadScheduledExecutor | 此线程池就是单线程的 newScheduledThreadPool 。 |
6 | WorkStealingPool | Java 8 新增创建线程池的方法,创建时如果不设置任何参数,则以当前机器处理器个数作为线程个数,此线程池会并行处理任务,不能保证执行顺序。 |
# 1. FixedThreadPool 使用
创建固定个数的线程池,具体示例如下:
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(2);
for (int i = 0; i < 3; i++) {
fixedThreadPool.execute(() -> {
System.out.println("CurrentTime - " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
以上程序执行结果如下:
CurrentTime - 2019-06-27 20:58:58
CurrentTime - 2019-06-27 20:58:58
CurrentTime - 2019-06-27 20:58:59
根据执行结果可以看出,newFixedThreadPool(2) 确实是创建了两个线程,在执行了一轮(2 次)之后,停了一秒,有了空闲线程,才执行第三次。
# 2. CachedThreadPool 使用
根据实际需要自动创建带缓存功能的线程池,具体代码如下:
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
cachedThreadPool.execute(() -> {
System.out.println("CurrentTime - " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
以上程序执行结果如下:
CurrentTime - 2019-06-27 21:24:46
CurrentTime - 2019-06-27 21:24:46
CurrentTime - 2019-06-27 21:24:46
CurrentTime - 2019-06-27 21:24:46
CurrentTime - 2019-06-27 21:24:46
CurrentTime - 2019-06-27 21:24:46
CurrentTime - 2019-06-27 21:24:46
CurrentTime - 2019-06-27 21:24:46
CurrentTime - 2019-06-27 21:24:46
CurrentTime - 2019-06-27 21:24:46
根据执行结果可以看出,newCachedThreadPool 在短时间内会创建多个线程来处理对应的任务,并试图把它们进行缓存以便重复使用。
# 4. SingleThreadExecutor 使用
创建单个线程的线程池,具体代码如下:
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 3; i++) {
singleThreadExecutor.execute(() -> {
System.out.println("CurrentTime - " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
以上程序执行结果如下:
CurrentTime - 2019-06-27 21:43:34
CurrentTime - 2019-06-27 21:43:35
CurrentTime - 2019-06-27 21:43:36
# 5. ScheduledThreadPool 使用
创建一个可以执行周期性任务的线程池,具体代码如下:
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(2);
scheduledThreadPool.schedule(() -> {
System.out.println("ThreadPool:" + LocalDateTime.now());
}, 1L, TimeUnit.SECONDS);
System.out.println("CurrentTime:" + LocalDateTime.now());
以上程序执行结果如下:
CurrentTime:2019-06-27T21:54:21.881
ThreadPool:2019-06-27T21:54:22.845
根据执行结果可以看出,我们设置的 1 秒后执行的任务生效了。
# 6. SingleThreadScheduledExecutor 使用
创建一个可以执行周期性任务的单线程池,具体代码如下:
ScheduledExecutorService singleThreadScheduledExecutor = Executors.newSingleThreadScheduledExecutor();
singleThreadScheduledExecutor.schedule(() -> {
System.out.println("ThreadPool:" + LocalDateTime.now());
}, 1L, TimeUnit.SECONDS);
System.out.println("CurrentTime:" + LocalDateTime.now());
# 7. WorkStealingPool 使用
Java 8 新增的创建线程池的方式,可根据当前电脑 CPU 处理器数量生成相应个数的线程池,使用代码如下:
ExecutorService workStealingPool = Executors.newWorkStealingPool();
for (int i = 0; i < 5; i++) {
int finalNumber = i;
workStealingPool.execute(() -> {
System.out.println("I:" + finalNumber);
});
}
Thread.sleep(5000);
以上程序执行结果如下:
I:0
I:3
I:2
I:1
I:4
根据执行结果可以看出,newWorkStealingPool 是并行处理任务的,并不能保证执行顺序。
# 8. 总结
Executors 可以创建 6 种不同类型的线程池,其中
newFixedThreadPool() 适合执行单位时间内固定的任务数
newCachedThreadPool() 适合短时间内处理大量任务
newSingleThreadExecutor() 和 newSingleThreadScheduledExecutor() 为单线程线程池。它们的区别在于:
newSingleThreadExecutor() 创建的线程池去执行任务时,都是一次性的,而
newSingleThreadScheduledExecutor() 可以执行周期性的任务。
newWorkStealingPool() 为 JDK 8 新增的并发线程池,可以根据当前电脑的 CPU 处理数量生成对比数量的线程池,但它的执行为并发执行不能保证任务的执行顺序。
# 9. 其它
【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 各个方法的弊端:
newFixedThreadPool 和 newSingleThreadExecutor :
主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至 OOM 。
newCachedThreadPool 和 newScheduledThreadPool :
主要问题是线程数最大数是 Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至 OOM 。