线程池的基本概述

​ 线程池是一种对线程进行复用和管理的机制。在 Java 中,通过线程池来控制线程的数量和线程的状态,提高线程的执行效率,避免频繁创建和销毁线程带来的资源消耗。就像餐厅的传菜员。餐厅有很多顾客下的订单(任务),而传菜员(线程)负责将菜送到顾客那里。如果每个订单都单独找一个传菜员,会导致传菜员数量过多,餐厅资源紧张。所以餐厅会安排固定的传菜员团队(线程池),他们负责不断地在厨房和顾客之间来回送菜(执行任务),提高服务效率。

image-20250517144746301

线程池的状态
image-20250517144928151

状态转换如图所示:
image-20250517155315553

线程池的简单使用流程

简单的使用流程可以总结为:任务提交 → 核心线程执行 → 任务队列缓存 → 非核心线程执行 → 拒绝策略处理
第一步,创建线程池。

第二步,调用线程池的 execute()方法,准备执行任务。

  • 如果正在运行的线程数量小于 corePoolSize,那么线程池会创建一个新的线程来执行这个任务;
  • 如果正在运行的线程数量大于或等于 corePoolSize,那么线程池会将这个任务放入等待队列;
  • 如果等待队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么线程池会创建新的线程来执行这个任务;
  • 如果等待队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会执行拒绝策略。

第三步,线程执行完毕后,线程并不会立即销毁,而是继续保持在池中等待下一个任务。

第四步,当线程空闲时间超出指定时间,且当前线程数量大于核心线程数时,线程会被回收。

执行流程图如下所示:image-20250517153337539

简单代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class ThreadPoolDemo {
public static void main(String[] args) {
// 创建一个线程池
ExecutorService threadPool = new ThreadPoolExecutor(
3, // 核心线程数
6, // 最大线程数
0, // 线程空闲时间
TimeUnit.SECONDS, // 时间单位
new LinkedBlockingQueue<>(10), // 等待队列
Executors.defaultThreadFactory(), // 线程工厂
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);
// 模拟 10 个线程办理业务
try {
for (int i = 1; i <= 10; i++) {
final int tempInt = i;
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "\t" + "办理业务" + tempInt);
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}

线程池的七个参数

ThreadPoolExecutor的构造方法如下所示:

1
2
3
4
5
6
7
8
9
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler
)

什么?白雪公主和他的七个小矮人?有点意思哈。下面讲讲每个参数的含义:

  • corePoolSize 核心线程数目

    含义:线程池中始终保留的最小线程数,即使这些线程处于空闲状态也不会被回收(除非调用 allowCoreThreadTimeOut(true)
    作用:保证在并发请求数少于等于该值时,所有任务都能立即得到线程执行,无需排队

  • maximumPoolSize 最大线程数目 核心线程数+救急线程数
    含义:线程池允许的最大线程数
    作用:当活动线程数达到 corePoolSize 且任务队列已满时,会继续创建新线程直至达到 maximumPoolSize;超过该阈值的新任务则 按拒绝策略处理

  • keepAliveTime 救急线程的存活时间
    含义:当线程数量超过 核心线程数目 时,多余的线程在空闲超过此时长后会被回收
    作用:控制线程池在负载低时的资源释放;unit 与之配合指定时间单位

  • unit 时间单位,针对救急线程——–控制救急线程生存时间对的单位
    含义:keepAliveTime 参数的时间单位,取值为 TimeUnit 枚举
    常用:TimeUnit.SECONDS(秒)TimeUnit.MILLISECOND(毫秒)

  • workQueue 阻塞队列
    含义:用于缓存提交但尚未执行的任务的阻塞队列,类型为 BlockingQueue<Runnable>
    作用:当线程数达到 corePoolSize 后,新任务会进入该队列等待空闲线程

  • threadFactory 线程工厂—-可以为线程创建时起个好听的名字?
    含义:用于创建新线程的工厂接口,默认实现可通过 Executors.defaultThreadFactory() 获取
    作用:可定制线程名、是否守护线程、优先级等

  • handler 拒绝策略
    含义:当线程池饱和(即线程数达 maximumPoolSize 且队列已满)时,对新提交任务的处理策略
    常见策略:

    • AbortPolicy:抛出 RejectedExecutionException(默认)
    • CallerRunsPolicy:由调用线程执行该任务
    • DiscardPolicy:直接丢弃任务
    • DiscardOldestPolicy:丢弃队列中最旧的一个任务,再尝试提交当前任务

一些代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 1. 固定大小线程池
ThreadPoolExecutor fixedPool = new ThreadPoolExecutor(
5, 5,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);

// 2. 缓存线程池
ThreadPoolExecutor cachedPool = new ThreadPoolExecutor(
0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy()
);

// 3. 自定义线程工厂和拒绝策略
ThreadFactory tf = runnable -> {
Thread t = new Thread(runnable);
t.setName("worker-" + t.getId());
return t;
};
RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardOldestPolicy();
ThreadPoolExecutor customPool = new ThreadPoolExecutor(
2, 10,
30L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100),
tf, handler
);

每个参数之间关系就是:任务优先使用核心线程执行,满了进入等待队列,队列满了启用非核心线程备用,线程池达到最大线程数量后触发拒绝策略,非核心线程的空闲时间超过存活时间就被回收

线程池的阻塞队列

  1. ArrayBlockingQueue
    • 特性:基于数组、有界(需在构造时指定容量)、采用 FIFO 原则
    • 使用场景:希望严格控制队列大小,避免内存占用过高
  2. LinkedBlockingQueue
    • 特性:基于链表,可选有界或无界(默认容量为 Integer.MAX_VALUE),FIFO。
    • 使用场景:任务生产、消费速度不确定时,可使用无界队列以防过早拒绝。
  3. PriorityBlockingQueue
    • 特性:基于堆,无界,按优先级(Comparator)排序,非 FIFO
    • 使用场景:需要优先执行高优先级任务时
  4. SynchronousQueue
    • 特性:零容量队列,生产者线程插入必须等待消费者线程取走才能继续。
    • 使用场景newCachedThreadPool() 使用,适合执行大量短生命周期任务,不保留队列
1
2
3
4
BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(50);
BlockingQueue<Runnable> lq = new LinkedBlockingQueue<>();
BlockingQueue<Runnable> pq = new PriorityBlockingQueue<>();
BlockingQueue<Runnable> sq = new SynchronousQueue<>();

Executors提供的一些工厂方法:

  1. newFixedThreadPoolimage-20250517160105093
  2. newCachedThreadPoolimage-20250517160043587
  3. newSingleThreadExecutorimage-20250517160019462

线程池提交任务—-submit与execute

execute 方法没有返回值,适用于不关心结果和异常的简单任务

1
2
3
4
5
threadsPool.execute(new Runnable() {
@Override public void run() {
System.out.println("execute() 方法提交的任务");
}
});

submit 有返回值,适用于需要获取结果或处理异常的场景

1
2
3
4
5
6
7
Future<Object> future = executor.submit(harReturnValuetask);
try { Object s = future.get(); }
catch (InterruptedException e | ExecutionException e) {
// 处理无法执行任务异常
} finally {
// 关闭线程池 executor.shutdown();
}

线程池的关闭

可以调用线程池的shutdownshutdownNow方法来关闭线程池

调用shutdown方法,线程池的状态变为SHUTDOWN
①不会再接收新的任务
②但是已经提交了的任务会执行完
③shutdown方法不会阻塞调用线程的执行

调用shutdownNow方法,线程池的状态变为STOP
①不会接收新任务
②会将队列中的任务返回
③并用 interrupt 的方式中断正在执行的任务
注意shutdownNow 不会真正终止正在运行的任务,只是给任务线程发送 interrupt 信号,任务是否能真正终止取决于线程是否响应 InterruptedException

线程池的异常处理

1、try-catch直接捕获处理异常

1
2
3
4
5
6
7
8
executor.execute(() -> {
try {
System.out.println("任务开始");
int result = 1 / 0; // 除零异常
} catch (Exception e) {
System.err.println("捕获异常:" + e.getMessage());
}
});

2、使用 Future 获取异常

1
2
3
4
5
6
7
8
9
10
11
Future<Object> future = executor.submit(() -> {
System.out.println("任务开始");
int result = 1 / 0; // 除零异常
return result;
});

try {
future.get();
} catch (InterruptedException | ExecutionException e) {
System.err.println("捕获异常:" + e.getMessage());
}

3、自定义 ThreadPoolExecutor 重写 afterExecute 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 2, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()) {
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
if (t != null) {
System.err.println("捕获异常:" + t.getMessage());
}
}
};

executor.execute(() -> {
System.out.println("任务开始");
int result = 1 / 0; // 除零异常
});

4、使用 UncaughtExceptionHandler·捕获异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 2, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
executor.setThreadFactory(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.err.println("捕获异常:" + e.getMessage());
}
});
return thread;
}
});

executor.execute(() -> {
System.out.println("任务开始");
int result = 1 / 0; // 除零异常
});

如果不关心任务返回值,建议使用 UncaughtExceptionHandler
如果关心任务返回值,建议使用 Future
如果想要全局捕获所有任务异常,建议重写 afterExecute 方法

来点面试题

解释 ThreadPoolExecutor 的七个参数及其作用。

当核心线程数已满且队列已满时,新任务如何处理?

LinkedBlockingQueueSynchronousQueue 的区别及适用场景。

如何自定义线程工厂与拒绝策略?请给出代码示例。

为什么要使用线程池而不是直接创建新线程?

如何监控线程池运行状态(如活跃线程数、队列大小、已完成任务数)?

allowCoreThreadTimeOut(true) 有何作用?

请简述实现一个简单线程池的思路。

在高并发场景下,如何调优 corePoolSizemaximumPoolSize 和队列容量?线程池如何调优

Executors.newFixedThreadPool()new ThreadPoolExecutor() 在底层有何区别?