1. 什么是线程池?
线程池 是一种用于管理线程的技术,它允许你复用线程以执行多个任务,从而减少了创建和销毁线程的开销。线程池会维护一组已经创建好的线程,当有任务到来时,线程池会分配一个线程来执行任务,而不是为每个任务都创建一个新的线程。
线程池的核心思想是通过线程复用来提高性能和资源的使用效率,尤其是在需要大量并发任务的场景下,线程池能够有效避免频繁创建和销毁线程带来的开销。
2. 为什么使用线程池?
减少线程创建和销毁的开销:每次创建和销毁线程都是有成本的。通过使用线程池,可以复用已有的线程,减少开销,提升性能。
控制线程数量:如果每个任务都创建一个新线程,可能会导致系统线程数过多,增加系统负担,甚至导致内存溢出。线程池允许控制同时运行的线程数量,防止过载。
提升响应速度:由于线程池中的线程是预先创建的,当有任务到来时可以立即执行,减少了创建线程的延迟。
统一的任务管理:线程池可以对任务进行统一的管理和调度,便于监控和调整。
线程池的核心概念
核心线程数(Core Pool Size):线程池中始终保持活动的最小线程数。
最大线程数(Maximum Pool Size):线程池中允许存在的最大线程数,超过这个数量时新任务会被拒绝。
任务队列:当线程数达到核心线程数时,新的任务会被放入队列中,等待可用的线程执行。
线程存活时间(Keep Alive Time):当线程池中的线程数超过核心线程数时,超过核心线程数的线程在没有任务执行时,最多保持多久的存活时间,之后会被销毁。
线程工厂(Thread Factory):用于创建新线程的工厂,通常用于给线程设置一些自定义的属性。
拒绝策略(Rejected Execution Handler):当任务过多无法处理时的策略。常见策略包括:抛出异常、丢弃任务、或者调用者自己处理。
3. 线程池的使用场景
需要执行大量短时间并发任务时。服务器应用,比如处理多个并发请求。需要控制并发线程数量的场景。
4. Java 中线程池的实现
在 Java 中,线程池主要通过 Executor 框架提供,常用的线程池实现包括 ThreadPoolExecutor、Executors 工具类提供的线程池方法。
Executors 工具类的常用线程池方法:
newFixedThreadPool(int nThreads):创建一个固定大小的线程池,线程池中的线程数始终为 nThreads。
newCachedThreadPool():创建一个可以根据需要创建新线程的线程池,但会对空闲线程进行回收。
newSingleThreadExecutor():创建一个只有一个线程的线程池,保证所有任务按顺序执行。
newScheduledThreadPool(int corePoolSize):创建一个可以定期或延时执行任务的线程池。
线程池的简单代码实现
下面是一个使用 ExecutorService 创建固定大小线程池的代码示例:
java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class Task implements Runnable {
private String taskName;
public Task(String taskName) {
this.taskName = taskName;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 执行任务:" + taskName);
try {
// 模拟任务执行时间
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 完成任务:" + taskName);
}
}
public class ThreadPoolExample {
public static void main(String[] args) {
// 创建一个固定大小为 3 的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(3);
// 提交 5 个任务到线程池
for (int i = 1; i <= 5; i++) {
Task task = new Task("任务" + i);
threadPool.submit(task);
}
// 关闭线程池,停止接受新任务
threadPool.shutdown();
}
}
运行结果示例:
pool-1-thread-1 执行任务:任务1
pool-1-thread-2 执行任务:任务2
pool-1-thread-3 执行任务:任务3
pool-1-thread-1 完成任务:任务1
pool-1-thread-1 执行任务:任务4
pool-1-thread-2 完成任务:任务2
pool-1-thread-2 执行任务:任务5
pool-1-thread-3 完成任务:任务3
pool-1-thread-1 完成任务:任务4
pool-1-thread-2 完成任务:任务5
5. 线程池知识点:
生命周期管理:线程池有四个主要状态:
Running:线程池正常接收任务并执行任务。
Shutdown:不再接收新任务,但执行未完成的任务。
Stop:不再接收新任务,终止正在执行的任务并抛弃等待的任务。
Terminated:所有任务执行完毕后,线程池进入终止状态。
可以通过 shutdown() 方法关闭线程池(会等待所有任务完成),或者通过 shutdownNow() 强制终止线程池中的所有任务。
任务提交:通过 submit() 提交任务,任务可以是实现了 Runnable 或 Callable 接口的对象。
Future:submit(Callable) 方法返回 Future 对象,Future 可以用来检查任务是否完成,获取任务的执行结果,或取消任务。
拒绝策略:当线程池无法接受新任务时,可以指定拒绝策略:
AbortPolicy(默认):抛出 RejectedExecutionException 异常。CallerRunsPolicy:由调用线程执行任务。DiscardPolicy:丢弃任务,不抛异常。DiscardOldestPolicy:丢弃最旧的任务,尝试提交新的任务。
6. 线程池的自定义实现
我们可以通过 ThreadPoolExecutor 来创建自定义的线程池,实现对核心线程数、最大线程数、队列、线程工厂、拒绝策略等进行更精细的控制。
自定义线程池代码示例:
java
import java.util.concurrent.*;
public class CustomThreadPoolExample {
public static void main(String[] args) {
// 创建自定义线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
2, // 核心线程数
5, // 最大线程数
60L, // 线程存活时间
TimeUnit.SECONDS, // 时间单位
new LinkedBlockingQueue<>(10), // 任务队列
new ThreadPoolExecutor.AbortPolicy()); // 拒绝策略
// 提交 15 个任务
for (int i = 1; i <= 15; i++) {
final int taskID = i;
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 执行任务:" + taskID);
try {
Thread.sleep(1000); // 模拟任务执行
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 完成任务:" + taskID);
});
}
// 关闭线程池
threadPool.shutdown();
}
}
总结
线程池 通过复用线程、控制线程数量来提高性能、减少系统开销。
ExecutorService 是线程池的核心接口,提供了多种线程池实现。
线程池 提供了各种灵活的选项,包括线程数量控制、任务队列、拒绝策略等,适用于高并发任务的场景。