Java 线程池原理
在 Java 里,线程池是一种用于管理和复用线程的机制。其核心思想是预先创建一定数量的线程,当有任务提交时,从线程池中取出一个空闲线程来执行该任务,任务执行完毕后,线程不会被销毁,而是重新回到线程池中等待下一个任务。这种方式可以避免频繁创建和销毁线程所带来的开销,提高系统的性能和资源利用率。
线程池的工作流程如下:
- 任务提交:当有新的任务提交到线程池时,线程池会根据当前的状态和配置来决定如何处理这个任务。
- 核心线程处理:如果线程池中的核心线程数量小于核心线程数的上限(
corePoolSize
),线程池会创建一个新的核心线程来执行这个任务。 - 任务队列:如果核心线程都在忙碌,且任务队列(
workQueue
)未满,新任务会被放入任务队列中等待执行。 - 创建新线程:如果任务队列已满,但线程池中的线程数量小于最大线程数(
maximumPoolSize
),线程池会创建一个新的非核心线程来执行这个任务。 - 拒绝策略:如果任务队列已满,且线程池中的线程数量已经达到最大线程数,线程池会根据预设的拒绝策略(
handler
)来处理这个新任务。
线程池的核心参数
Java 中创建线程池的核心类是 ThreadPoolExecutor
,其构造函数有多个参数,下面是几个核心参数及其对线程池行为的影响:
1. corePoolSize
(核心线程数)
- 含义:线程池中的核心线程数量。当有新任务提交时,线程池会优先创建核心线程来执行任务,直到核心线程数量达到
corePoolSize
。 - 影响:如果
corePoolSize
设置得较小,线程池在处理大量任务时可能会因为核心线程不足而导致任务在队列中等待,影响系统的响应时间;如果设置得过大,会占用过多的系统资源,增加系统的开销。
2. maximumPoolSize
(最大线程数)
- 含义:线程池允许创建的最大线程数量。当任务队列已满,且核心线程都在忙碌时,线程池会继续创建新的线程,直到线程数量达到
maximumPoolSize
。 - 影响:如果
maximumPoolSize
设置得太小,当任务量突然增大时,可能会导致任务被拒绝;如果设置得太大,会占用过多的系统资源,甚至可能导致系统崩溃。
3. keepAliveTime
(线程空闲时间)
- 含义:当线程池中的线程数量超过
corePoolSize
时,多余的空闲线程在等待新任务的最长时间。如果在这个时间内没有新任务到来,这些空闲线程会被销毁。 - 影响:如果
keepAliveTime
设置得太短,可能会导致线程频繁地创建和销毁,增加系统的开销;如果设置得太长,会占用过多的系统资源。
4. TimeUnit
(时间单位)
- 含义:
keepAliveTime
的时间单位,例如TimeUnit.SECONDS
表示秒,TimeUnit.MILLISECONDS
表示毫秒等。 - 影响:它决定了
keepAliveTime
的具体时间尺度,不同的时间单位会影响线程的空闲等待时间。
5. workQueue
(任务队列)
- 含义:用于存储等待执行的任务的队列。当核心线程都在忙碌时,新任务会被放入任务队列中等待执行。常见的任务队列有
ArrayBlockingQueue
、LinkedBlockingQueue
、SynchronousQueue
等。 - 影响:不同类型的任务队列具有不同的特性,会影响线程池的行为。例如,
ArrayBlockingQueue
是有界队列,当队列满时,新任务会根据拒绝策略进行处理;LinkedBlockingQueue
可以是有界或无界队列,如果是无界队列,当核心线程都在忙碌时,新任务会一直放入队列中,不会创建新的线程,直到系统资源耗尽。
6. threadFactory
(线程工厂)
- 含义:用于创建线程的工厂类。通过自定义线程工厂,可以为线程设置名称、优先级等属性。
- 影响:可以方便地对线程进行管理和调试,例如在日志中更容易区分不同线程的执行情况。
7. handler
(拒绝策略)
- 含义:当任务队列已满,且线程池中的线程数量已经达到
maximumPoolSize
时,线程池会根据预设的拒绝策略来处理新提交的任务。常见的拒绝策略有AbortPolicy
(直接抛出异常)、CallerRunsPolicy
(使用调用者所在的线程来执行任务)、DiscardPolicy
(直接丢弃任务)、DiscardOldestPolicy
(丢弃队列中最老的任务,然后尝试提交新任务)。 - 影响:不同的拒绝策略会影响线程池在任务过载时的处理方式,需要根据具体的业务场景选择合适的拒绝策略。
下面是一个创建 ThreadPoolExecutor
的示例代码:
import java.util.concurrent.*;
public class ThreadPoolExample {
public static void main(String[] args) {
int corePoolSize = 2;
int maximumPoolSize = 5;
long keepAliveTime = 60;
TimeUnit timeUnit = TimeUnit.SECONDS;
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(10);
ThreadFactory threadFactory = Executors.defaultThreadFactory();
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
timeUnit,
workQueue,
threadFactory,
handler
);
// 提交任务
for (int i = 0; i < 15; i++) {
final int taskId = i;
threadPool.submit(() -> {
System.out.println("Task " + taskId + " is being executed by " + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 关闭线程池
threadPool.shutdown();
}
}
在这个示例中,我们创建了一个 ThreadPoolExecutor
线程池,并提交了 15 个任务。根据线程池的配置,核心线程数为 2,最大线程数为 5,任务队列大小为 10,拒绝策略为 AbortPolicy
。当任务数量超过线程池的处理能力时,会抛出 RejectedExecutionException
异常。