一、线程池概述
1、线程池的优势
线程池是一种线程使用模式,线程过多会带来调度开销,进而影响缓存局部性和整体性能,而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务,这避免了在处理短时间任务时创建与销毁线程的代价,线程池不仅能够保证内核的充分利用,还能防止过分调度
线程池的工作主要是控制运行的线程数量,在处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行
2、线程池的特点
降低资源消耗: 通过重复利用已创建的线程降低线程创建和销毁造成的销耗
提高响应速度: 当任务到达时,任务可以不需要等待线程创建就能立即执行
提高线程的可管理性: 线程是稀缺资源,如果无限制的创建,不仅会销耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控
二、线程池的架构
- Java 中的线程池是通过 Executor 框架实现的,该框架中用到了 Executor 接口,Executors 类,ExecutorService 类,ThreadPoolExecutor 类

三、线程池的种类与创建
1、一池 N 线程
(1)基本介绍
线程池中的线程处于一定的量,可以很好的控制线程的并发量
线程可以重复被使用,在显示关闭之前,都将一直存在
超出一定量的线程被提交时候需在队列中等待
(2)创建方式
ExecutorService threadPool = Executors.newFixedThreadPool(5);
(3)适用场景
- 适用于可以预测线程数量的业务,或者服务器负载较重,对线程数有严格限制的场景
(4)基本使用
- 使用 5 个窗口处理 10 个顾客请求
ExecutorService threadPool = Executors.newFixedThreadPool(5);
try {
for (int i = 1; i <= 10; i++) {
// 执行线程
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName()+" 办理业务");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭线程
threadPool.shutdown();
}
- 结果
pool-1-thread-1 办理业务
pool-1-thread-5 办理业务
pool-1-thread-4 办理业务
pool-1-thread-3 办理业务
pool-1-thread-2 办理业务
pool-1-thread-3 办理业务
pool-1-thread-4 办理业务
pool-1-thread-5 办理业务
pool-1-thread-1 办理业务
pool-1-thread-2 办理业务
2、一池一线程
(1)基本介绍
- 线程池中最多执行一个线程,之后提交的线程活动将会排在队列中依次执行
(2)创建方式
ExecutorService threadPool = Executors.newSingleThreadExecutor();
(3)适用场景
- 适用于需要保证顺序执行各个任务,并且在任意时间点,不会同时有多个线程的场景
(4)基本使用
- 使用 1 个窗口处理 10 个顾客请求
ExecutorService threadPool = Executors.newSingleThreadExecutor();
try {
for (int i = 1; i <= 10; i++) {
// 执行线程
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName()+" 办理业务");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭线程
threadPool.shutdown();
}
- 结果
pool-1-thread-1 办理业务
pool-1-thread-1 办理业务
pool-1-thread-1 办理业务
pool-1-thread-1 办理业务
pool-1-thread-1 办理业务
pool-1-thread-1 办理业务
pool-1-thread-1 办理业务
pool-1-thread-1 办理业务
pool-1-thread-1 办理业务
pool-1-thread-1 办理业务
3、一池可扩容线程
(1)基本介绍
线程池中数量没有固定,可达到最大值为 Interger. MAX_VALUE
线程池中的线程可进行缓存重复利用和回收,默认回收时间为一分钟
当线程池中没有可用线程时,会重新创建一个线程
(2)创建方式
ExecutorService threadPool = Executors.newCachedThreadPool();
(3)适用场景
- 适用于创建一个可无限扩大的线程池,服务器负载压力较轻,执行时间较短,任务多的场景
(4)基本使用
- 处理 20 个顾客请求
ExecutorService threadPool = Executors.newCachedThreadPool();
try {
for (int i = 1; i <= 20; i++) {
// 执行线程
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName()+" 办理业务");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭线程
threadPool.shutdown();
}
- 结果
pool-1-thread-1 办理业务
pool-1-thread-4 办理业务
pool-1-thread-3 办理业务
pool-1-thread-2 办理业务
pool-1-thread-6 办理业务
pool-1-thread-5 办理业务
pool-1-thread-8 办理业务
pool-1-thread-7 办理业务
pool-1-thread-3 办理业务
pool-1-thread-4 办理业务
pool-1-thread-7 办理业务
pool-1-thread-5 办理业务
pool-1-thread-1 办理业务
pool-1-thread-2 办理业务
pool-1-thread-6 办理业务
pool-1-thread-8 办理业务
pool-1-thread-3 办理业务
pool-1-thread-9 办理业务
pool-1-thread-7 办理业务
pool-1-thread-5 办理业务
四、线程池的底层原理
1、ThreadPoolExecutor 类
- 通过查看上面三种方式创建线程池对象的源代码,发现都有 new ThreadPoolExecutor 操作,具体查看该类的源代码,其构造器涉及七个参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
2、ThreadPoolExecutor 构造器涉及的七个参数
int corePoolSize:常驻线程数量
int maximumPoolSize:最大线程数量
long keepAliveTime:空闲线程存活时间
TimeUnit unit:空闲线程存时间单位
BlockingQueue workQueue:阻塞队列,存放提交但未执行任务
ThreadFactory threadFactory:线程工厂,用于创建线程(可省略)
RejectedExecutionHandler handler:阻塞队列满时的拒绝策略(可省略)
五、线程池的工作流程

在创建了线程池后,线程池中的线程数为零
当调用 execute 方法添加一个请求任务时,线程池会做如下判断
如果正在运行的线程数量小于 corePool 的线程数量,那么马上创建线程运行这个任务
如果正在运行的线程数量大于或等于 corePool 的线程数量,那么将这个任务存入阻塞队列
如果这个时候队列已满且正在运行的线程数量小于 maximumPool 的线程数量,则创建非核心线程立刻运行这个任务
如果队列已满且正在运行的线程数量大于或等于 maximumPool 的线程数量,那么线程池会启动拒绝策略
当一个线程完成任务时,它会从队列中取下一个任务来执行
当一个线程空闲超过一定的时间时,会做如下判断
如果当前运行的线程数大于 corePool 的线程数量,那么这个线程就被停掉
线程池的所有任务完成后,最终会收缩到 corePool 的大小
六、线程池的拒绝策略
1、 AbortPolicy
- 默认的拒绝策略,会直接抛出 RejectedExecutionException 异常阻止系统正常运行
2、DiscardPolicy
- 该策略会丢弃无法处理的任务,不给予任何处理也不抛出异常,如果允许任务丢失,这是最好的一种策略
3、DiscardOldestPolicy
- 丢弃队列中等待最久的任务,然后把当前任务存入队列中,并尝试提交当前任务
4、CallerRunsPolicy
- 该策略既不会丢弃任务,也不会抛出异常,而是将任务退回给调用者
七、自定义线程池
1、Executors 返回的线程池对象的弊端
FixedThreadPool 和 SingleThreadExecutor 允许请求队列的长度为 Interger.MAX_VALUE,可能会堆积大量异常, 从而导致 OOM
CachedThreadPool 允许的创建线程数量为 Interger.MAX_VALUE,可能会创建大量线程,从而导致 OOM
2、具体实现
package com.my.pool;
import java.util.concurrent.*;
public class MyThreadPoolTest {
public static void main(String[] args) {
ExecutorService threadPool = new ThreadPoolExecutor(
2,
5,
2L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy());
// 处理 10 个顾客请求
try {
for (int i = 1; i <= 10; i++) {
// 执行线程
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName()+" 办理业务");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭线程
threadPool.shutdown();
}
}
}
- 结果
pool-1-thread-1 办理业务
pool-1-thread-4 办理业务
pool-1-thread-3 办理业务
pool-1-thread-2 办理业务
pool-1-thread-3 办理业务
pool-1-thread-4 办理业务
pool-1-thread-1 办理业务
pool-1-thread-5 办理业务