Java 里面线程池的顶级接口是 Executor,但是严格意义上讲 Executor 并不是一个线程池,而只是一个执行线程的工具,真正的线程池接口是 ExecutorService。Executors 作用主要用来创建线程池,相当于一个线程工厂,但是在阿里巴巴的《Java开发手册》中明确指出:
虽然不推荐使用,但是可以了解一下 Executors ,方便理解线程池。
Executors 可以创建六中以下线程池:
- FixedThreadPool(n):创建一个数量固定的线程池,超出的任务会在队列中等待空闲的线程,可用于控制程序的最大并发数。
- CachedThreadPool():短时间内处理大量工作的线程池,会根据任务数量产生对应的线程,并试图缓存线程以便重复使用,如果限制 60 秒没被使用,则会被移除缓存。
- SingleThreadExecutor():创建一个单线程线程池。
- ScheduledThreadPool(n):创建一个数量固定的线程池,支持执行定时性或周期性任务。
- SingleThreadScheduledExecutor():此线程池就是单线程的 newScheduledThreadPool。
- WorkStealingPool(n):Java 8 新增创建线程池的方法,创建时如果不设置任何参数,则以当前机器处理器个数作为线程个数,此线程池会并行处理任务,不能保证执行顺序。
下面分别来看以上六种线程池的具体代码使用:
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 - 2020-01-19 08:57:40
CurrentTime - 2020-01-19 08:57:40
CurrentTime - 2020-01-19 08:57:41
newFixedThreadPool(2) 创建了两个线程,将前两个线程放入线程池中并执行,第三个线程进行等待,当前面两个其中的某一个执行完后,有了空闲线程,第三个线程就会执行。
CachedThreadPool 使用
根据实际需要自动创建带缓存功能的线程池。
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
cachedThreadPool.execute(() -> {
System.out.println("Current Time - " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
newCachedThreadPool 在短时间内会创建多个线程来处理对应的任务,并试图把它们进行缓存以便重复使用。
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();
}
});
}
newSingleThreadExecutor() 会创建一个线程池(这个线程池只有一个线程),这个线程池可以在线程结束后(或发生异常时)重新启动一个线程来替代原来的线程继续执行下去。
ScheduledThreadPool 使用
创建一个可以执行周期性任务的线程池。
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(2);
scheduledThreadPool.schedule(() -> {
System.out.println("ThreadPool:" + LocalDateTime.now());
}, 1L, TimeUnit.SECONDS);
System.out.println("CurrentTime:" + LocalDateTime.now());
输出结果如下:
2020-01-22T19:36:49.973
ThreadPool:2020-01-22T19:36:50.931
根据执行结果可以看出,我们设置的 1 秒后执行的任务生效了。
SingleThreadScheduledExecutor 使用
创建一个可以执行周期性任务的单线程池。
ScheduledExecutorService singleThreadScheduledExecutor = Executors.newSingleThreadScheduledExecutor();
singleThreadScheduledExecutor.schedule(() -> {
System.out.println("ThreadPool:" + LocalDateTime.now());
}, 1L, TimeUnit.SECONDS);
System.out.println("CurrentTime:" + LocalDateTime.now());
WorkStealingPool 使用
Java 8 新增的创建线程池的方式:工作窃取线程池,其可根据当前电脑 CPU 处理器内核数量生成相应个数的线程池,其非常适合使用在很耗时的操作。但是 newWorkStealingPool 不是 ThreadPoolExecutor 的扩展,它是新的线程池类 ForkJoinPool 的扩展,但是都可以使用 Executors 类来创建。
int cores = Runtime.getRuntime().availableProcessors();
ExecutorService workStealingPool = Executors.newWorkStealingPool();
workStealingPool.execute(() -> {
try {
TimeUnit.SECONDS.sleep(1);
System.out.println("Thread1: " + Thread.currentThread().getName() + " - " + LocalDateTime.now());
} catch (InterruptedException e) {
e.printStackTrace();
}
});
for (int i = 0; i < cores; i++) {
workStealingPool.execute(() -> {
try {
TimeUnit.SECONDS.sleep(4);
System.out.println("Thread2: " + Thread.currentThread().getName() + " - " + LocalDateTime.now());
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 因为 workStealingPool 是 Deamon 线程,即后台线程,精灵线程,守护线程;所以当main方法结束时, 此方法虽然还在后台运行,但是无输出,这时就可以通过对主线程阻塞来进行输出
System.in.read();
以上输出结果如下:
Thread1: ForkJoinPool-1-worker-1 - 2020-01-23T16:25:43.386
Thread2: ForkJoinPool-1-worker-3 - 2020-01-23T16:25:46.331
Thread2: ForkJoinPool-1-worker-0 - 2020-01-23T16:25:46.331
Thread2: ForkJoinPool-1-worker-2 - 2020-01-23T16:25:46.331
Thread2: ForkJoinPool-1-worker-1 - 2020-01-23T16:25:47.387
newWorkStealingPool 是并行处理任务的,并不能保证执行顺序。因为我的电脑是 4 核,所以 newWorkStealingPool 会创建只能容纳 4 个线程的线程池,当第一个 Thread1 添加到线程池后,又添加了 4 个线程,但是最后的一个线程会等待,只有前 4 个线程会执行;第一个线程延时 1s 先输出,然后继续输出后 3 个线程;当 1s 的输出完之后,会窃取最后一个等待的线程并执行;从上面输出结果的时间中就可以体现出这一点。
总结
Executors 可以创建 6 种不同类型的线程池,其中 newFixedThreadPool() 适合执行单位时间内固定的任务数,newCachedThreadPool() 适合短时间内处理大量任务,newSingleThreadExecutor() 和 newSingleThreadScheduledExecutor() 为单线程线程池,而 newSingleThreadScheduledExecutor() 可以执行周期性的任务,是 newScheduledThreadPool(n) 的单线程版本,而 newWorkStealingPool() 为 JDK 8 新增的并发线程池,可以根据当前电脑的 CPU 处理数量生成对比数量的线程池,但它的执行为并发执行不能保证任务的执行顺序。
文章部分内容参考《老王的 GitChat 专栏》:https://gitbook.cn/gitchat/column/5d493b4dcb702a087ef935d9/topic/5d4e13ae69004b174cd00140