线程池(ThreadPool)是一种基于池化技术的多线程管理机制,用于减少在创建和销毁线程上所花的开销和资源消耗。线程池中的线程可以循环利用,用于执行多个任务。
线程池的工作原理
线程池主要解决两个问题:首先,通过重复利用一系列已经创建好的线程,减少线程创建和销毁的性能开销;其次,通过对任务队列和线程的调度,可以细致地控制最大并发数,从而优化系统资源的使用和系统稳定性。
线程池的工作流程大致如下:
- 任务提交:将执行任务提交到线程池。如果线程池中的线程少于核心线程数,则创建新的线程执行这个任务;如果线程数已达到核心线程数,任务会被加入到队列中等待执行。
- 任务排队:如果队列也已满,且当前线程数小于最大线程数,则创建新的线程来处理被提交的任务;如果线程数已达到最大值,则根据饱和策略处理无法执行的任务。
- 任务执行:线程从队列中取出任务并执行。
- 线程闲置:在执行完任务后,线程不会立即销毁,而是可用于执行其他任务。
- 线程销毁:如果线程超过一定时间(keepAliveTime)没有任务执行,且当前线程数大于核心线程数,则这个线程会被销毁,以节省资源。
Java 中的线程池
在 Java 中,线程池是通过 Executor
框架实现的,具体来说是通过 ExecutorService
接口及其实现类,如 ThreadPoolExecutor
和 ScheduledThreadPoolExecutor
。
下面是 ThreadPoolExecutor
的一部分源码,简化和注释以解释其核心部分:
public class ThreadPoolExecutor extends AbstractExecutorService {
// 线程池的主要状态,包含在一个原子变量中
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// 状态在高位,工作线程数在低位
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
// ... 省略其他状态
// 线程工厂
private final ThreadFactory threadFactory;
// 阻塞队列
private final BlockingQueue<Runnable> workQueue;
// 超时时间
private final long keepAliveTime;
// 线程池关闭的处理
private final RejectedExecutionHandler handler;
// 锁
private final ReentrantLock mainLock = new ReentrantLock();
// ... 省略其他成员
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
// ... 初始化线程池
}
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
// 如果工作线程数小于核心线程数,则尝试创建新线程执行任务
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// 如果线程池正在运行且任务能加到队列中,则完成任务提交
if (isRunning(c) && workQueue.offer(command)) {
// 二次检查
int recheck = ctl.get();
if (!isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 如果无法提交任务,则尝试创建新线程执行任务,如果失败,则执行拒绝策略
else if (!addWorker(command, false))
reject(command);
}
// ... 省略其他方法
}
这段代码虽然没有展示 ThreadPoolExecutor
的全部复杂性,但它揭示了一些核心组成部分:
ctl
:这是一个AtomicInteger
,它打包了线程池的状态和线程数量信息。workQueue
:一个阻塞队列,用于存储等待执行的任务。keepAliveTime
:非核心线程空闲时的存活时长。threadFactory
:用来创建新线程的工厂。handler
:线程池饱和时的策略接口。
示例代码
演示如何使用 ThreadPoolExecutor
:
import java.util.concurrent.*;
public class ThreadPoolExample {
public static void main(String[] args) {
// 创建线程池
ExecutorService executor = new ThreadPoolExecutor(
2, // 核心线程数
4, // 最大线程数
100, // 保持存活时间
TimeUnit.SECONDS, // 时间单位
new LinkedBlockingQueue<>(10), // 任务队列
Executors.defaultThreadFactory(), // 线程工厂
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);
// 提交任务
for (int i = 0; i < 10; i++) {
int finalI = i;
executor.execute(() -> {
System.out.println(Thread.currentThread().getName() + " is executing task " + finalI);
// 模拟耗时操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
// 关闭线程池
executor.shutdown();
}
}
当你运行这个示例时,你会看到一个 ThreadPoolExecutor
是如何初始化和使用的。线程池首先尝试使用核心线程来执行任务,如果核心线程忙碌,则任务会被加入队列中。如果队列已满,线程池将创建新的线程(直到达到最大线程数)。如果线程数达到最大值并且队列也满了,新提交的任务将被拒绝处理。
线程池是并发编程中使用频率非常高的组件。了解其内部原理和适当的使用方法对于开发高效、稳定的并发应用至关重要。