文章目录
线程池核心原理
线程池的核心原理是基于“池化”(Pooling)思想,这种思想在计算机科学中广泛应用,例如数据库连接池、对象池等。线程池的主要目的是为了减少线程创建和销毁的开销,提高线程的复用性,从而提高程序的性能和响应速度。以下是线程池的核心原理:
- 线程复用:线程池中维护了一组工作线程(Worker Threads),这些线程被创建后不会立即销毁,而是用于执行多个任务。当任务到达时,线程池会从池中选出一个空闲的线程来执行任务,而不是每次都创建新线程。
- 任务队列:线程池通常配备了一个任务队列(Work Queue),用于存储待执行的任务。当所有工作线程都处于忙碌状态时,新提交的任务会被放入队列中等待空闲线程。
- 线程管理:线程池内部会管理线程的生命周期,包括线程的创建、销毁、工作线程的数量等。线程池会根据配置的核心线程数(Core Pool Size)和最大线程数(Maximum Pool Size)来控制线程的数量。
- 任务分配:线程池中的任务分配器(Task Scheduler)负责从任务队列中取出任务,并将任务分配给空闲的工作线程。
- 动态线程管理:线程池可以根据当前负载动态地调整线程数量。例如,当任务队列满了,并且所有核心线程都处于忙碌状态时,线程池可能会创建新的线程,直到达到最大线程数。
- 拒绝策略:当线程池达到最大线程数,并且任务队列已满时,线程池会根据预定义的拒绝策略来处理新提交的任务。常见的拒绝策略包括抛出异常、直接丢弃任务、丢弃队列中最老的任务等。
ThreadPoolExecutor
ThreadPoolExecutor 是 Java 中线程池的核心实现,位于 java.util.concurrent 包下。它提供了线程池的基本功能,包括线程的创建、任务的执行、线程的回收等,并允许开发者自定义线程池的行为。
主要构造函数:
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- corePoolSize:核心线程数,即始终存在的线程数量。
- maximumPoolSize:最大线程数,即线程池中允许的最大线程数量。
- keepAliveTime:非核心线程的空闲存活时间。
- unit:keepAliveTime 的时间单位。
- workQueue:用于存放任务的阻塞队列。
- threadFactory:线程工厂,用于创建新线程。
- handler:拒绝策略,用于处理当线程池和队列都满时新提交的任务。
执行任务:
ThreadPoolExecutor 提供了多个方法来执行任务:
- execute(Runnable command):执行一个 Runnable 任务。
- submit(Runnable task):提交一个 Runnable 任务,并返回一个 Future 对象,可以通过这个对象获取任务执行的结果(如果有)。
- submit(Callable task):提交一个 Callable 任务,并返回一个 Future 对象,可以通过这个对象获取任务执行的结果。
关闭线程池:
ThreadPoolExecutor 可以通过以下方法来关闭:
- shutdown():启动线程池的关闭序列,不再接受新任务,但会继续执行所有已经提交的任务。
- shutdownNow():尝试立即停止所有正在执行的任务,并返回尚未开始执行的任务列表。
线程池生命周期:
ThreadPoolExecutor 定义了线程池的生命周期状态,包括:
- RUNNING:接受新任务并处理队列中的任务。
- SHUTDOWN:不接受新任务,但处理队列中的任务。
- STOP:不接受新任务,不处理队列中的任务,并尝试中断正在执行的任务。
- TIDYING:所有任务都已终止,线程池正在转换为 TERMINATED 状态。
- TERMINATED:线程池已终止。
Executor框架
Executor框架是Java 5引入的一个用于并发编程的框架,它提供了任务执行和异步任务管理的工具。这个框架的主要目的是简化线程的使用和管理,使得开发者可以更加专注于任务的逻辑,而不是线程的创建和管理。Executor框架的主要组件包括:
Executor接口:
- 这是框架的基础,一个简单的接口,定义了执行任务的方法 execute(Runnable command)。
ExecutorService接口:
- 继承自Executor接口,提供了更多管理任务执行的方法,如 submit()、shutdown()、shutdownNow() 等。
- 它还提供了用于批量执行任务的方法,如 invokeAll() 和 invokeAny()。
ThreadPoolExecutor类:
- ExecutorService接口的一个实现,是一个灵活的线程池实现,允许开发者自定义线程池的参数和行为。
ScheduledExecutorService接口:
- 继承自ExecutorService,提供了定时任务执行的方法,如 schedule()、scheduleAtFixedRate()、scheduleWithFixedDelay()。
ScheduledThreadPoolExecutor类:
- ScheduledExecutorService接口的一个实现,用于执行定时任务和周期性任务。
Future接口:
- 表示异步计算的结果,提供了检查计算是否完成、等待计算完成、获取计算结果、取消计算等方法。
Callable接口:
- 与Runnable类似,但它允许任务返回一个结果或抛出一个异常。
Executor框架的使用通常涉及以下步骤:
- 创建ExecutorService实例,例如通过ThreadPoolExecutor或Executors工厂类。
- 提交Runnable或Callable任务到ExecutorService。
- 使用Future来获取任务的结果(如果任务提交时使用了submit()方法)。
- 关闭ExecutorService,防止新任务的提交,并等待所有任务执行完成。
线程池实战
使用线程池的实战通常涉及到java.util.concurrent包中的ExecutorService和ThreadPoolExecutor类。以下是一个简单的示例,展示了如何使用线程池来执行多个任务。
步骤1:创建线程池
首先,我们需要创建一个线程池。我们可以使用Executors工厂类来创建一个固定大小的线程池,也可以直接使用ThreadPoolExecutor构造函数来定制线程池。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// 创建一个固定大小的线程池,包含5个线程
ExecutorService executorService = Executors.newFixedThreadPool(5);
步骤2:创建任务
接下来,我们创建一个实现Runnable接口的任务类。
import java.util.concurrent.TimeUnit;
public class MyTask implements Runnable {
private final int taskId;
public MyTask(int taskId) {
this.taskId = taskId;
}
@Override
public void run() {
System.out.println("开始执行任务:" + taskId);
try {
// 模拟任务执行需要一段时间
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务:" + taskId + " 执行完成");
}
}
步骤3:提交任务到线程池
现在我们可以将任务提交到线程池中执行。
for (int i = 0; i < 10; i++) {
executorService.submit(new MyTask(i));
}
步骤4:关闭线程池
最后,当我们不再需要线程池时,应该关闭它以释放资源。
executorService.shutdown();
完整示例
将上述步骤组合在一起,我们得到一个完整的线程池使用示例。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ThreadPoolExample {
public static void main(String[] args) {
// 创建一个固定大小的线程池,包含5个线程
ExecutorService executorService = Executors.newFixedThreadPool(5);
// 提交任务到线程池
for (int i = 0; i < 10; i++) {
executorService.submit(new MyTask(i));
}
// 关闭线程池
executorService.shutdown();
}
}
class MyTask implements Runnable {
private final int taskId;
public MyTask(int taskId) {
this.taskId = taskId;
}
@Override
public void run() {
System.out.println("开始执行任务:" + taskId);
try {
// 模拟任务执行需要一段时间
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务:" + taskId + " 执行完成");
}
}
我们创建了一个包含5个线程的线程池,并提交了10个任务。由于线程池的大小限制,一次只能执行5个任务,其他任务会在队列中等待。每个任务模拟执行2秒,然后输出完成信息。
在实际应用中,线程池的使用会更加复杂,可能需要根据应用程序的特定需求来调整线程池的大小和配置,以及处理任务执行中的异常和结果。