Java线程池的实现主要通过`java.util.concurrent.ThreadPoolExecutor`类来完成,它是Java并发包中的核心类之一,提供了强大的线程池管理能力。下面通过分析`ThreadPoolExecutor`的部分关键源代码来详细说明其工作原理和使用方法。
### 创建线程池
首先,我们来看如何创建一个线程池。以下是一个简单的示例,展示了如何使用`ThreadPoolExecutor`的构造函数来创建一个线程池:```java
import java.util.concurrent.*;
public class ThreadPoolExample {
public static void main(String[] args) {
// 核心线程数
int corePoolSize = 5;
// 最大线程数
int maximumPoolSize = 10;
// 空闲线程存活时间
long keepAliveTime = 60L;
// 时间单位
TimeUnit unit = TimeUnit.SECONDS;
// 任务队列
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(100);
// 线程工厂,用于创建新线程
ThreadFactory threadFactory = Executors.defaultThreadFactory();
// 拒绝策略
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
// 创建线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue,
threadFactory,
handler
);
// 提交任务到线程池
for (int i = 0; i < 20; i++) {
executor.execute(new RunnableTask());
}
// 关闭线程池
executor.shutdown();
}
static class RunnableTask implements Runnable {
@Override
public void run() {
System.out.println("Task executed by " + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
```
### `ThreadPoolExecutor`关键属性和构造函数参数
- `corePoolSize`:线程池的核心线程数,即使线程空闲,除非设置了`allowCoreThreadTimeOut`为true,否则这部分线程也会一直存活。
- `maximumPoolSize`:线程池能够容纳的最大线程数。
- `keepAliveTime`:非核心线程闲置时的超时时长,超过这个时间会终止线程。
- `unit`:用于时间量度的单位,如`TimeUnit.SECONDS`。
- `workQueue`:任务队列,用于保存等待执行的任务,常用的有`LinkedBlockingQueue`、`ArrayBlockingQueue`等。
- `threadFactory`:用于创建新线程的工厂。
- `handler`:拒绝策略,当线程池和任务队列都满时,用于处理新的任务请求,常见的有`AbortPolicy`(抛出异常)、`CallerRunsPolicy`(调用者线程执行任务)、`DiscardPolicy`(丢弃任务)、`DiscardOldestPolicy`(丢弃队列最旧的任务并尝试重新提交当前任务)。
### 核心方法
- `execute(Runnable command)`:提交一个不需要返回值的任务执行,这是线程池最核心的方法。
- `submit(Callable<T> task)`:提交一个有返回值的任务执行,并返回一个Future对象用于获取结果。
- `shutdown()`:启动有序关闭,停止接收新任务,但已提交的任务会继续执行直到完成。
- `shutdownNow()`:尝试停止所有正在执行的任务,并停止处理新任务,返回等待执行的任务列表。
### 源码解析简述
`ThreadPoolExecutor`内部维护了一个原子整数`ctl`,它用高3位表示线程池的状态(RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED),低29位表示当前活动线程数。通过`ctl`的CAS操作来管理线程池状态和线程数量,保证了线程安全。
在`execute`方法中,线程池会根据当前的线程数和任务队列情况,决定是创建新线程、加入任务队列还是拒绝任务。如果需要创建新线程,它会通过`threadFactory`创建,并将其封装成`Worker`对象,然后启动这个线程去执行任务队列中的任务。
以上是`ThreadPoolExecutor`的基础使用和部分源码原理的简要介绍,实际上线程池的实现远比此复杂,还包括了工作窃取、线程复用、线程中断、任务拒绝处理等多种高级特性。
### 线程池的工作流程
了解线程池的工作流程对于深入理解其机制非常重要。下面是`ThreadPoolExecutor`执行任务的一个简化流程:
1. **任务提交**:当通过`execute()`方法提交一个任务时,线程池会执行以下判断逻辑:
- 如果当前运行的线程数少于核心线程数(`corePoolSize`),即使线程池中有空闲线程,也会直接创建一个新线程来执行任务。
- 如果当前运行的线程数等于核心线程数,任务会被放入任务队列中排队等待执行。
- 如果任务队列已满,并且当前运行的线程数小于最大线程数(`maximumPoolSize`),则会创建新的线程来执行任务。
- 如果以上条件都不满足,即线程数达到最大并且任务队列也满了,此时会触发拒绝策略处理这个任务。
2. **线程复用**:一旦线程创建,除非线程池关闭或者线程被允许超时(通过`allowCoreThreadTimeOut(true)`配置),否则线程会一直存活,等待执行新的任务。这意味着任务执行完毕后,线程并不会立即销毁,而是进入空闲状态,等待下一个任务到来。
3. **线程回收**:对于超过核心线程数的线程(即非核心线程),在空闲时间超过`keepAliveTime`后,会自动终止。核心线程是否允许超时取决于构造函数的配置。
4. **任务队列**:线程池中的任务队列是线程池的重要组成部分,它决定了任务的排队策略。常见的队列有无界队列(如`LinkedBlockingQueue`,可能导致任务无限堆积)、有界队列(如`ArrayBlockingQueue`,限制了任务队列的大小,有助于防止资源耗尽)、同步队列(如`SynchronousQueue`,不存储任务,直接将任务交给线程执行,适合大量短任务快速处理的场景)。
5. **拒绝策略**:当线程池和任务队列都达到了它们的容量上限,新提交的任务将会由拒绝策略处理。Java提供了几种内置的拒绝策略:
- `AbortPolicy`:默认策略,抛出`RejectedExecutionException`异常。
- `CallerRunsPolicy`:调用者所在线程自己去执行该任务。
- `DiscardPolicy`:直接丢弃任务,不执行也不抛出异常。
- `DiscardOldestPolicy`:丢弃队列中最旧的任务,然后尝试重新提交被拒绝的任务。
### 扩展知识
- **线程池监控**:`ThreadPoolExecutor`提供了丰富的监控接口,如`getPoolSize()`、`getActiveCount()`、`getQueue()`等,可以帮助开发者监控线程池的运行状态。
- **自定义线程池**:虽然`Executors`类提供了便捷方法来创建不同类型的线程池,但为了更精确地控制线程池的行为,推荐直接使用`ThreadPoolExecutor`构造函数自定义线程池参数。
- **工作窃取(Work Stealing)**:Java的`ForkJoinPool`是一个特殊的线程池,实现了工作窃取算法,适用于大量相互独立的任务。在这种模型下,空闲的线程可以从其他繁忙线程的任务队列中“窃取”任务来执行,提高了多核处理器的利用率。
理解线程池的运作机制和合理配置线程池参数,对于提升程序的性能、响应时间和资源利用率至关重要。