# 线程池简介
线程池是用于实现计算机程序中执行并发性的软件设计模式。线程池维护多个线程,等待由监督程序分配任务以便并发执行。
相较于普通创建线程的方式,线程池有以下两个个优点:
- 线程可以复用。线程使用完不会销毁,避免频繁创建和销毁线程降低系统的效率。
- 可以减少并发线程的数量。线程池可以控制线程的数量,防止创建大量线程降低性能。
- 提供定时执行、定期执行、单线程、并发数控制等功能。
当线程使用的时间较短,或者需要大量创建线程时,就可以使用线程池。
# Java 的线程池
ThreadPoolExecutor 类实现了线程池,下图是 ThreadPoolExecutor 类的继承关系。
Executor 接口是线程的执行接口,只有一个方法 execute。
void execute(Runnable command); |
ExecutorService 接口继承 Executor 接口,添加了关闭线程、提交线程等方法。其中,提交线程的方法就相当于 execute 方法。
<T> Future<T> submit(Callable<T> task); | |
<T> Future<T> submit(Runnable task, T result); | |
Future<?> submit(Runnable task); |
execute 方法和 submit 方法的区别是:
- 接受参数不一样。
- submit 有返回值,execute 没有返回值。
- submit 方便异常处理。
AbstractExecutorService 是一个抽象类,对父接口的一些方法提供了默认实现。
# 线程池原理
下面是线程池的几个核心属性。
private final BlockingQueue<Runnable> workQueue; // 线程的阻塞队列 | |
private volatile ThreadFactory threadFactory; // 线程的工厂 | |
private volatile RejectedExecutionHandler handler; // 线程的饱和策略 | |
private volatile long keepAliveTime; // 线程超出核心数量的空闲线程的存活时间 | |
private volatile int corePoolSize; // 线程的核心数量 | |
private volatile int maximumPoolSize; // 线程的最大数量 |
- 若当前线程数量少于 corePoolSize,就创建一个新的线程,无论线程池中是否有空闲的线程;
- 若当前线程数量大于 corePoolSize 小于 maximumPoolSize,就将线程放入阻塞对列等待执行;
- 若当前线程数量小于 maximumPoolSize,阻塞对列已满,就直接创建新的线程进行处理;
- 若当前线程数量等于 maximumPoolSize,并且阻塞队列已满,就使用饱和策略。
Java 有以下几种阻塞队列:
ArrayBlockingQueue
由数组实现的阻塞队列,FIFO,有界。
LinkedBlockingQueue
由链表实现的阻塞队列,FIFO,无界。
SynchronousQueue
没有存储空间的阻塞队列,任务提交给它之后必须要交给一条工作线程处理,如果当前没有空闲线程,则新建一个线程。无界。
PriorityBlockingQueue
具有优先级的阻塞队列。
线程池有四种饱和策略:
AbortingPolicy
直接抛异常。
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
CallerRunsPolicy
只用调用者所在的线程执行任务。
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
DiscardOldestPolicy
丢弃当前任务。
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
DiscardPolicy
抛弃最久的任务。
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
# 常见几种线程池
Executors 类提供了几个构建线程池的方法。
查看 Executors 类的源码可知,Executors 就是通过构造 ThreadPoolExecutor 实现的线程池。
newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
- 线程池核心线程数量为 0,最大线程数量为 Integer 最大值。
- 只要线程空闲 60s,就会被销毁。
- 使用 SynchronousQueue 阻塞队列,只要没有空闲线程,就会创建新的线程来执行任务。
newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
- 线程池大小固定,核心数量和最大数量均为 N=nThreads。
- 超多核心线程数量的线程一旦空闲下来就会被销毁,但是这里该参数无效,因为最大线程数量等于核心线程数量。
- 阻塞队列使用基于链表的阻塞队列,所以阻塞队列没有大小限制,可以无限接受任务。
newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
- 核心线程和最大线程数量都为 1。
- 使用基于链表的阻塞队列。
# 线程池的使用
使用起来十分简单。例如,使用固定大小的线程池。
public static void main(String[] args) { | |
ExecutorService cachedThreadPool = Executors.newWorkStealingPool(2); | |
for (int i = 0; i < 10; i++) { | |
cachedThreadPool.execute(() -> { | |
for (int j = 0; j < 10; j++) { | |
System.out.println(Thread.currentThread().getName()); | |
} | |
}); | |
} | |
cachedThreadPool.shutdown(); | |
} |
只需要创建的一个线程池,然后线程池执行 Runnable 方法就可以了。
然后在实际使用中,我们最好不要直接使用 Executors 创建的线程池,最好我们自己来创建一个 ThreadPoolExecutor。
在阿里巴巴代码规约中有这样一条规则:
【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:
1)FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2)CachedThreadPool 和 ScheduleThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
# Spring 中的线程池
Spring 封装了 Java 的多线程的实现,我们一般使用 ThreadPoolTaskExecutor 创建线程池。
配置如下:
使用时直接调用 execute 方法即可。
# 几种线程池总结
方法 | 描述 | 使用场景 |
---|---|---|
newCachedThreadPool | 必要时创建新线程,空闲线程会被保留 60 秒 | 比较适合处理执行时间比较短的任务 |
newFixedThreadPool | 该池包含固定数量的线程,空闲线程会一直被保留 | 可以用于已知并发压力的情况下,对线程数做限制 |
newSingleThreadExecutor | 只有一个线程的 “池”,该线程顺序执行每一个提交的任务 | 可以用于保证顺序执行的场景,并且只有一个线程在执行 |
newScheduledThreadPool | 用于预定执行而构建的固定线程池 | 适用于需要多个后台线程执行周期任务的场景 |
newSingleThreadScheduledExecutor | 用于预定执行而构建的单线程 “池” | 适用于需要一个后台线程执行周期任务的场景 |
newWorkStealingPool | 线程池拥有多个任务队列,可以减少连接数 | 创建当前可用 CPU 数量的线程来并行执行 |