在并发编程中,线程是一种宝贵的资源。合理地创建和管理线程,不仅能提升程序性能,还能避免系统资源被耗尽。线程池(Thread Pool) 正是为此而生的利器。

什么是线程池

线程池可以理解为线程的容器或线程的管理者。它内部维护了一组可复用的线程,并通过合理调度来执行提交的任务,从而避免频繁创建和销毁线程的性能开销。
线程池由三部分组成:

  • 核心线程(core thread)
    • 线程池中始终保留的线程数,即使空闲也不回收。
  • 非核心线程(临时线程)
    • 当任务太多且核心线程都在忙,线程池会创建非核心线程来帮忙。
    • 一段时间(keepAliveTime)内空闲就会销毁。
  • 任务队列(workQueue)
    • 提交的任务如果所有线程都忙,就会被放入队列等待执行

为什么需要线程池?

降低频繁的创建线程和销毁线程
控制最大并发线程量

ThreadPoolExecutor 函数参数详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 非核心线程存活时间
TimeUnit unit, // 存活时间单位
BlockingQueue<Runnable> workQueue, // 阻塞任务队列
ThreadFactory threadFactory, // 线程工厂(可定制线程名、优先级)
RejectedExecutionHandler handler // 拒绝策略
)


ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, // 核心线程数
4, // 最大线程数
10, // 非核心线程存活时间
TimeUnit.SECONDS, // 时间单位:秒
new LinkedBlockingQueue<>(10), // 任务队列最大 10
Executors.defaultThreadFactory(), // 默认线程工厂
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略:抛异常
);

注意: LinkedBlockingQueue 默认容量为 Integer.MAX_VALUE,不指定会有内存溢出的风险。

Executor框架和Executor工具类

Java 的线程池并非孤立存在,而是属于整个 Executor 框架

Executor 是 Java 并发包中的一套统一的线程池使用 API 接口规范,你可以理解为线程池的“控制台”。

  • 核心接口ExecutorExecutorService
  • 常见实现类
    • ThreadPoolExecutor(核心线程池实现)
    • ScheduledThreadPoolExecutor(定时任务线程池)
接口 说明
Executor 最顶层接口,定义 execute()
ExecutorService 扩展接口,支持 submit()invokeAll()
ScheduledExecutorService 定时/周期执行任务接口
Executors 是一个工具类,提供常见线程池的工厂方法:
1
2
3
4
Executors.newFixedThreadPool(n);        // 固定大小线程池
Executors.newCachedThreadPool(); // 无界线程池(可无限扩容)
Executors.newSingleThreadExecutor(); // 单线程池
Executors.newScheduledThreadPool(n); // 支持定时任务线程池

阿里巴巴开发者手册不推荐使用 Executors 创建线程池,建议直接使用 ThreadPoolExecutor 自定义参数,避免资源不可控。
为什么禁止使用Executors创建线程池?-阿里云开发者

代码举例

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
public class ThreadPoolDemo {
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2,
4,
5,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(5),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝时由提交线程执行任务
);

for (int i = 0; i < 10; i++) {
int taskId = i;
executor.submit(() -> {
System.out.println("任务 " + taskId + " 由线程 " + Thread.currentThread().getName() + " 执行");
try {
Thread.sleep(1000); // 模拟任务耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}

executor.shutdown();
}
}

某一次运行结果

1
2
3
4
5
任务 0 由线程 pool-1-thread-1 执行
任务 4 由线程 main 执行 //这是由于拒绝策略导致的,我们所选拒绝策略CallerRunsPolicy
任务 3 由线程 pool-1-thread-2 执行
任务 1 由线程 pool-1-thread-1 执行
任务 2 由线程 pool-1-thread-2 执行

注意,如果在这里你有这样的疑问:
为什么不是任务0,任务1···这样顺序的进行?
这是因为CPU调度问题,先开始的线程不一定先执行完成。