1.什么是Java线程池
Java线程池(Java Thread Pool)是Java并发编程中的一个重要概念。它是一种为多线程处理任务提供容器的工具,用于管理和控制线程的执行。线程池的主要目标是减少在创建和销毁线程上的开销,以及优化系统资源的使用,提高线程的可管理性。
当有一个新任务需要执行时,线程池会做如下处理:
-
如果线程池内的线程数量还没达到最小线程数,线程池就会创建一个新线程来执行任务。
-
如果线程池内的线程数量达到了最小线程数但还没达到最大线程数,并且任务队列已满,线程池会创建一个新线程来执行任务。
-
如果线程池内的线程数量达到了最大线程数,任务队列也已满,线程池会拒绝处理任务,并按照预先设定的策略(拒绝策略)来处理这种情况。
Java线程池在java.util.concurrent包下,常用的类有ThreadPoolExecutor和Executors。ThreadPoolExecutor是线程池的核心实现类,可以创建不同类型的线程池。Executors提供了一些工厂方法来创建不同类型的线程池,如固定大小的线程池、单线程池、可缓存线程池等。
使用线程池可以带来很多好处,比如:
-
降低资源消耗:通过重复利用已创建的线程,降低线程创建和销毁造成的消耗。
-
提高响应速度:当任务到达时,任务可以不需要等待线程创建就能立即执行。
-
提高线程的可管理性:线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,而且难以控制。使用线程池可以进行统一的分配、调优和监控。
2. Java线程池的工作过程
Java线程池的工作过程相当于一个生产者消费者模型。"生产者"是产生任务的,"消费者"就是线程池中的线程,负责处理这些任务。这个过程可以分为以下几个步骤:
-
任务提交:调用者(即任务的生产者)通过调用线程池的execute()方法来提交需要异步处理的任务。这些任务不会立刻被执行,而是被保存起来,等待线程来处理。
-
任务接收和存储:线程池接收到新的任务后,首先判断池中的线程数量是否达到
corePoolSize(核心线程数)。如果没有,那么线程池会创建一个新线程来处理提交的任务。如果已经达到,那么线程池会试图将任务存入内部的任务队列(workQueue)中,等待被空闲的线程处理。 -
任务处理:线程池中的线程会循环从任务队列中取任务来执行。当任务队列为空时,如果线程数量大于
corePoolSize,那么非核心线程会在指定的keepAliveTime(保持活跃时间)后退出。如果线程数量不大于corePoolSize,那么线程会无限期等待新的任务。需要注意的是,如果设置了allowCoreThreadTimeOut(允许核心线程超时),那么核心线程在等待新任务时也会在keepAliveTime后退出。 -
任务队列满:如果任务队列已满且线程数量已经达到
maximumPoolSize(最大线程数),那么线程池会根据设置的RejectedExecutionHandler(拒绝策略)来处理新提交的任务。比如,AbortPolicy会直接抛出异常,CallerRunsPolicy会让调用者线程自己处理任务。 -
任务完成:线程完成任务后,会回到线程池中,等待处理下一个任务。如果所有的任务都已经完成,那么线程池中的所有线程会等待新的任务。在没有新的任务提交时,非核心线程会在
keepAliveTime后退出,核心线程(如果设置了allowCoreThreadTimeOut)也会在keepAliveTime后退出。 -
线程池关闭:调用者可以调用线程池的shutdown()或shutdownNow()方法来关闭线程池。shutdown()会让线程池进入关闭状态,但是线程池会继续处理所有已提交的任务,然后退出。shutdownNow()会试图停止所有正在执行的任务,并立即关闭线程池。
在Java线程池的工作过程中,除了上述的参数外,还有一个ThreadFactory(线程工厂)参数,用于自定义如何创建新的线程
,例如设置线程的名称、优先级等。
3. 简单案例
下面我将使用一个简单的Java例子来展示上述过程。
首先,我们创建一个具有特定参数的线程池:
import java.util.concurrent.*;
// 创建一个线程池,其中核心线程数和最大线程数都是2,保持活跃时间是60秒,任务队列使用LinkedBlockingQueue
ExecutorService executorService = new ThreadPoolExecutor(
2, // corePoolSize: 核心线程数
2, // maximumPoolSize: 最大线程数
60, // keepAliveTime: 保持活跃时间
TimeUnit.SECONDS, // 时间单位
new LinkedBlockingQueue<Runnable>(5), // 任务队列
Executors.defaultThreadFactory(), // 默认线程工厂
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略:直接抛出异常
);
然后,我们提交10个任务到线程池:
try {
for (int i = 0; i < 10; i++) {
final int taskNumber = i;
executorService.execute(() -> {
System.out.println("开始处理任务: " + taskNumber);
try {
// 模拟处理任务需要2秒钟
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("结束处理任务: " + taskNumber);
});
}
} catch (RejectedExecutionException e) {
// 如果任务队列满了且线程数量达到maximumPoolSize,会触发RejectedExecutionException
e.printStackTrace();
}
你会发现这个程序抛出了RejectedExecutionException异常,这是因为我们一共提交了10个任务,但是线程池的最大线程数加上任务队列的大小只能处理7个任务。所以,当我们试图提交第8个任务时,由于任务队列已满且线程数量已达到最大,线程池会根据设置的AbortPolicy拒绝策略抛出异常。
最后,我们需要记得在适当的时机关闭线程池:
executorService.shutdown();
需要注意的是,线程池并不会立刻关闭,它会先处理完已提交的任务。如果你想立刻关闭线程池,可以使用shutdownNow()方法,但是这可能会导致正在处理的任务被中断。