什么是线程池?
线程池是一种用于管理和复用线程的机制。
线程池的核心思想是预先创建一定数量的线程,并把它们保存在线程池中,当有任务需要执行时,线程池会从空闲线程中取出一个线程来执行该任务。任务执行完毕后,线程不是被销毁,而是返还给线程池,可以立即或稍后被再次用来执行其他任务。这种机制可以避免因频繁创建和销毁线程而带来的性能开销,同时也能控制同时运行的线程数量,从而提高系统的性能和资源利用率。
线程池的主要组成部分包括工作线程、任务队列、线程管理器等。线程池的设计有助于优化多线程程序的性能和资源利用,同时简化了线程的管理和复用的复杂性。
线程池有什么好处?
- 减少线程创建和销毁的开销,线程的创建和销毁需要消耗系统资源,线程池通过复用线程,避免了对资源的频繁操作,从而提高系统性能;
- 控制和优化系统资源利用,线程池通过控制线程的数量,可以尽可能地压榨机器性能,提高系统资源利用率;
- 提高响应速度,线程池可以预先创建线程且通过多线程并发处理任务,提升任务的响应速度及系统的并发性能;
线程池状态
RUNNING
:线程池一旦被创建,就处于RUNNING
状态,任务数为0
,能够接收新任务,对已排队的任务进行处理。SHUTDOWN
:不接收新任务,但能处理已排队的任务。当调用线程池的shutdown()
方法时,线程池会由RUNNING
转变为SHUTDOWN
状态。STOP
:不接收新任务,不处理已排队的任务,并且会中断正在处理的任务。当调用线程池的shutdownNow()
方法时,线程池会由RUNNING
或SHUTDOWN
转变为STOP
状态。TIDYING
:当线程池在SHUTDOWN
状态下,任务队列为空且执行中任务为空,或者线程池在STOP
状态下,线程池中执行中任务为空时,线程池会变为TIDYING
状态,会执行terminated()
方法。这个方法在线程池中是空实现,可以重写该方法进行相应的处理。TERMINATED
:线程池彻底终止。线程池在TIDYING
状态执行完terminated()
方法后,就会由TIDYING
转变为TERMINATED
状态。
线程状态
- 初始(
NEW
):新创建了一个线程对象,但还没有调用start()
方法。 - 运行(
RUNNABLE
):Java
线程中将就绪(READY
)和运行中(RUNNING
)两种状态笼统的称为"运行"。
a.就绪(READY
):线程对象创建后,其他线程(比如main
线程)调用了该对象的start()
方法。该状态的线程位于可运行线程池中,等待被线程调度选中并分配cpu
使用权 。
b.运行中(RUNNING
):就绪(READY
)的线程获得了cpu
时间片,开始执行程序代码。
- 阻塞(
BLOCKED
):表示线程阻塞于锁。 - 等待(
WAITING
):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。 - 超时等待(
TIMED_WAITING
):该状态不同于WAITING
,它可以在指定的时间后自行返回。 - 终止(
TERMINATED
):表示该线程已经执行完毕。
拒绝策略
线程池的拒绝策略决定了在任务队列已满的情况下,如何处理新提交的任务。
AbortPolicy
- 这是默认的拒绝策略,当线程池无法接受新任务时,会抛出RejectedExecutionException
异常。这意味着新任务会被立即拒绝,不会加入到任务队列中,也不会执行。通常情况下都是使用这种拒绝策略。DiscardPolicy
- 这个策略在任务队列已满时,会丢弃新的任务而且不会抛出异常。新任务提交后会被默默地丢弃,不会有任何提示或执行。这个策略一般用于日志记录、统计等不是非常关键的任务。DiscardOldestPolicy
- 这个策略也会丢弃任务,但它会先尝试将任务队列中最早的任务删除,然后再尝试提交新任务。如果任务队列已满,且线程池中的线程都在工作,可能会导致一些任务被丢弃。这个策略对于一些实时性要求较高的场景比较合适。CallerRunsPolicy
- 这个策略将任务回退给调用线程,而不会抛出异常。调用线程会尝试执行任务。这个策略可以降低任务提交速度,适用于任务提交者能够承受任务执行的压力,但希望有一种缓冲机制的情况。
❝
一般来说,默认的拒绝策略还是比较常用的,因为大多数情况下我们不太会让任务多到线程池中放不下,要不然就提升执行速度,要不然就提升队列长度了。
工作原理
- 任务提交:当有新任务提交到线程池时,线程池会根据当前状态决定如何处理该任务。
- 核心线程处理:如果当前运行的线程数少于核心线程数(
corePoolSize
),线程池会立即创建一个新线程来执行任务,即使其他核心线程处于空闲状态。 - 任务队列缓冲:如果当前运行的线程数等于或大于核心线程数,新任务会被放入任务队列(
workQueue
)中等待执行。 - 最大线程处理:如果任务队列已满且运行的线程数少于最大线程数(
maximumPoolSize
),线程池会创建新的线程来处理任务。 - 拒绝策略执行:如果任务队列已满且运行的线程数等于最大线程数,线程池会执行拒绝策略(
RejectedExecutionHandler
)来处理新提交的任务。 - 线程回收:当线程完成任务后,如果空闲时间超过
keepAliveTime
,非核心线程会被回收,以减少资源消耗。
如何使用
创建线程池
arduino
public class ThreadPoolUtils {
/**
* 线程池
*/
private static ExecutorService executor = initDefaultExecutor();
/**
* 统一的获取线程池对象方法
*/
public static ExecutorService getExecutor() {
return executor;
}
private static final int DEFAULT_THREAD_SIZE = 16;
private static final int DEFAULT_QUEUE_SIZE = 10240;
private static ExecutorService initDefaultExecutor() {
return new ThreadPoolExecutor(
DEFAULT_THREAD_SIZE, // 核心线程数
DEFAULT_THREAD_SIZE, // 最大线程数
300, TimeUnit.SECONDS, // 线程空闲时间
new ArrayBlockingQueue<>(DEFAULT_QUEUE_SIZE), // 任务队列
new DefaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()); // 拒绝策略
}
}
创建一个简单的任务类,并将其提交到线程池中执行:
java
class MyTask implements Runnable {
private final int taskId;
public MyTask(int taskId) {
this.taskId = taskId;
}
@Override
public void run() {
System.out.println("Task " + taskId + " is being executed by thread " + Thread.currentThread().getName());
try {
// 模拟任务执行时间
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("Task " + taskId + " has been completed.");
}
}
public class ThreadPoolExample {
public static void main(String[] args) {
ExecutorService executor = ThreadPoolUtils.getExecutor();
// 提交任务到线程池
for (int i = 1; i <= 20; i++) {
executor.submit(new MyTask(i));
}
// 关闭线程池
executor.shutdown();
try {
// 等待所有任务执行完毕
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
// 如果等待超时,中断线程池中的线程
executor.shutdownNow();
}
} catch (InterruptedException e) {
// 如果在等待过程中被中断,中断线程池中的线程
executor.shutdownNow();
Thread.currentThread().interrupt();
}
}
}