线程池
为什么需要线程池
Java 添加线程池工具是为了:
问题 |
线程池的解决方案 |
带来的好处 |
频繁创建/销毁线程开销大 |
线程复用 |
提升性能,降低资源消耗 |
线程创建导致延迟 |
预创建线程 |
提高响应速度 |
无限制创建线程风险高 |
统一管理,控制数量 |
提高系统稳定性和可管理性 |
功能单一 |
提供定时、拒绝策略等 |
增强功能性和健壮性 |
因此,线程池是构建高性能、高稳定性、可管理多线程应用程序的核心基础和必备工具。在 Java 中,java.util.concurrent
包下的线程池框架是并发编程的基石,几乎在所有需要处理并发任务的服务器端应用中都会用到。
线程池的创建方法
Java 中线程池的创建方式主要有两大类:
- 通过
Executors
工厂类(快速创建):提供了几种预设的线程池配置,方便快捷,适用于常见场景。
- 通过
ThreadPoolExecutor
构造函数(手动创建):提供了所有可配置参数,允许开发者进行更精细化的控制,这是《阿里巴巴Java开发手册》等规范推荐的方式,以避免使用Executors
可能带来的风险。
方式一:使用Executors工厂类
Executors
是一个工具类,它提供了多个静态工厂方法来创建不同配置的线程池。
newFixedThreadPool - 固定大小线程池
1 2 3 4 5 6 7 8 9
| ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
fixedThreadPool.execute(() -> { System.out.println("任务被执行,线程:" + Thread.currentThread().getName()); });
fixedThreadPool.shutdown();
|
- 特点:创建一个固定大小的线程池。池中的线程数始终不变。
- 工作机制:
- 如果所有线程都在活动状态,新任务会在无界队列(
LinkedBlockingQueue
)中等待,直到有线程可用。
- 适用场景:适用于为了满足资源管理的需求,需要限制当前线程数量的场景,适用于负载较重的服务器。
newCachedThreadPool - 可缓存线程池
1
| ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
|
- 特点:创建一个可缓存的线程池。
- 工作机制:
- 如果池中有空闲线程,则重用它们。
- 如果没有空闲线程,则创建新线程添加到池中。
- 默认情况下,闲置 60 秒的线程会被从池中移除。因此,长时间空闲的池不会消耗任何资源。
- 使用的任务队列是同步移交队列(
SynchronousQueue
),它不存储元素,每个插入操作必须等待另一个线程的对应移除操作。
- 适用场景:适用于执行很多短期异步任务的小程序,或者是负载较轻的服务器。注意:任务数量激增时,可能会创建大量线程,导致资源耗尽。
newSingleThreadExecutor - 单线程线程池
1
| ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
|
- 特点:创建一个单线程化的线程池。
- 工作机制:它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。同样使用无界队列。
- 适用场景:适用于需要保证任务顺序执行,并且在任意时间点,不会有多个线程是活动的场景。
newScheduledThreadPool - 定时任务线程池
1 2 3 4 5 6 7 8 9 10 11 12
| ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
scheduledThreadPool.schedule(() -> System.out.println("5秒后执行"), 5, TimeUnit.SECONDS);
scheduledThreadPool.scheduleAtFixedRate(() -> System.out.println("延迟1秒后,每3秒执行一次"), 1, 3, TimeUnit.SECONDS);
scheduledThreadPool.scheduleWithFixedDelay(() -> { System.out.println("任务结束,等待2秒后再次执行"); }, 1, 2, TimeUnit.SECONDS);
|
- 特点:创建一个固定大小的线程池,支持定时及周期性任务执行。
- 适用场景:需要执行延迟或周期性任务,如心跳检测、数据同步等。
方式二:手动配置ThreadPoolExecutor
直接使用 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 // 拒绝策略 )
|
1 2 3 4 5 6 7 8
| ThreadPoolExecutor executor = new ThreadPoolExecutor( 10, 10, 5, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
|
7大核心参数详解
corePoolSize
(核心线程数):线程池中始终保持存活的线程数量,即使它们是空闲的。
maximumPoolSize
(最大线程数):线程池允许创建的最大线程数量。
keepAliveTime
(非核心空闲线程存活时间):当线程数大于核心线程数时,多余的空闲线程在终止前等待新任务的最长时间。
unit
(时间单位):keepAliveTime
的时间单位(如 TimeUnit.SECONDS
)。
workQueue
(任务队列/阻塞队列):用于保存等待执行的任务的阻塞队列。选择不同的队列决定了线程池的排队策略。
ArrayBlockingQueue
:有界队列,需要指定大小。
LinkedBlockingQueue
:无界队列(默认容量为 Integer.MAX_VALUE
),FixedThreadPool
和 SingleThreadExecutor
使用它。
SynchronousQueue
:不存储元素的队列,每个插入操作必须等待一个移除操作,否则插入操作会一直阻塞。CachedThreadPool
使用它。
PriorityBlockingQueue
:具有优先级的无界阻塞队列。
threadFactory
(线程工厂):用于创建新线程的工厂。可以用来设置线程名、线程组、优先级等,便于排查问题。
handler
(拒绝策略):当线程池和队列都已满时,如何处理新提交的任务。JDK 提供了4种策略:
ThreadPoolExecutor.AbortPolicy
(默认):抛出 RejectedExecutionException
异常。
ThreadPoolExecutor.CallerRunsPolicy
:由提交任务的调用者线程(通常是 main 线程)自己来执行这个任务。
ThreadPoolExecutor.DiscardPolicy
:直接丢弃新任务,不做任何处理。
ThreadPoolExecutor.DiscardOldestPolicy
:丢弃队列中最老的一个任务,然后尝试再次提交当前任务。
线程池工作原理

线程池关闭
1 2 3 4 5 6 7 8
| executorService.shutdown();
executorService.shutdownNow();
executorService.awaitTermination(5, TimeUnit.SECONDS)
|
submit和execute方法的区别
特性 |
execute() |
submit() |
方法来源 |
Executor 接口 |
ExecutorService 接口 |
参数类型 |
仅接受 Runnable |
接受 Runnable 或 Callable |
返回值 |
无返回值 (void ) |
返回 Future<?> 对象 |
异常处理 |
任务中的异常会直接抛出(在线程中) |
异常被封装在 Future 中,调用 get() 时抛出 |
用途 |
简单的异步执行,不关心结果 |
需要获取执行结果或处理异常的任务 |