1. Java线程池深入剖析与手工实现教程
1.1 Java线程池核心原理
1.1.1 线程池的概念和优势
在Java中,线程是操作系统最基本的执行单元,而线程池是管理线程的一种机制。线程池能够合理地复用线程资源,减少线程创建和销毁的开销,有效管理线程生命周期,同时也提供了强大的任务调度功能。使用线程池可以提高响应速度,降低资源消耗,增强程序的可扩展性。
1.1.2 Java线程池工作流程
线程池的工作流程主要包括任务提交、任务存储、任务执行、任务管理和线程管理五个部分。当一个任务被提交到线程池时,线程池会首先判断核心线程是否都在执行任务,如果不是,则创建一个新的工作线程来执行任务;如果核心线程都在工作,则将任务存入工作队列;如果工作队列已满,线程池会创建非核心线程来处理任务;超出线程数量上限之后,线程池会拒绝处理任务,并执行拒绝策筥。
1.1.3 线程池的状态和核心参数
Java线程池维护了几种状态来反映其生命期的不同阶段,如RUNNING、SHUTDOWN等。此外,线程池的行为受到多个参数的调控,关键的参数包括corePoolSize(核心线程数)、maximumPoolSize(最大线程数)、keepAliveTime(线程空闲存活时间)和workQueue(任务队列)。
1.1.4 ThreadPoolExecutor类的关键组成
ThreadPoolExecutor是Java线程池实现的核心类,它实现了ExecutorService接口。类内部基于ReentrantLock实现线程安全的任务队列操作,采用了先进的工作窃取算法和多种拒绝策略来优化多线程任务的处理。
2. 手撸Java线程池
2.1 定义核心字段
在实现自定义线程池前,我们需要定义一些核心字段来支撑线程池的基本功能。以下是必需的核心字段:
- 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; - 最大线程容量。
这些字段为线程池的状态控制、任务执行、线程创建和销毁等提供了可操作的数值基础。
2.2 创建内部类WorkerThread
WorkerThread类是线程池中实际执行任务的工作线程。这个内部类将扩展Thread类,并持有一个Runnable类型的任务对象引用。其基本结构如下:
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
final Thread thread;
Runnable firstTask;
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
public void run() {
runWorker(this);
}
// 省略其他实现细节...
}
2.3 构造ThreadPool类
构造一个ThreadPool类时,我们需要指定核心线程数、最大线程数、空闲线程存活时间、时间的单位、工作队列以及线程工厂。构造函数示例如下:
public ThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.workQueue = workQueue;
this.threadFactory = threadFactory;
}
2.4 实现任务执行方法
线程池需要提供任务提交和执行任务的接口。这通常通过execute()方法来实现,它接受一个Runnable对象作为任务,并进行适当的处理。示例实现如下:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
// 省略任务提交至队列或直接创建新线程的逻辑...
}
2.5 完整源码展示与解析
接下来,我们将实现一个简单的线程池。这个线程池具备基本的任务提交和处理功能。 首先定义我们的线程池类SimpleThreadPool:
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
public class SimpleThreadPool {
// 线程池的基本大小
private volatile int corePoolSize;
// 线程池最大数量
private volatile int maximumPoolSize;
// 任务队列
private final BlockingQueue<Runnable> taskQueue;
// 当前正在执行任务的线程数量
private final AtomicInteger currentPoolSize = new AtomicInteger(0);
public SimpleThreadPool(int corePoolSize, int maximumPoolSize) {
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.taskQueue = new LinkedBlockingQueue<>();
}
public void execute(Runnable task) {
if (task == null) {
throw new NullPointerException("The Runnable task is null");
}
// 线程池未满,直接创建线程执行任务
if (currentPoolSize.get() < corePoolSize) {
if (addWorker(task)) {
return;
}
}
// 线程池满了,将任务加入队列
if (!taskQueue.offer(task)) {
if (!addWorker(task)) {
// 无法添加任务时的拒绝策略处理
reject(task);
}
}
}
// 添加一个工作线程来处理任务
private boolean addWorker(Runnable firstTask) {
if (currentPoolSize.getAndIncrement() < maximumPoolSize) {
new Thread(() -> {
try {
Runnable task = firstTask;
while (task != null || (task = getTask()) != null) {
task.run();
task = null;
}
} finally {
currentPoolSize.getAndDecrement();
}
}).start();
return true;
} else {
currentPoolSize.getAndDecrement();
return false;
}
}
// 从任务队列中获取任务
private Runnable getTask() {
try {
return taskQueue.take();
} catch (InterruptedException e) {
// 处理中断异常
Thread.currentThread().interrupt();
return null;
}
}
// 任务无法处理时的策略
private void reject(Runnable task) {
// 这里简单打印日志并抛出异常
System.out.println("Task " + task.toString() + " rejected.");
throw new RuntimeException("Task " + task.toString() + " rejected.");
}
}
在上面的例子中,我们定义了一个简单的线程池类SimpleThreadPool。它有两个构造函数参数:corePoolSize(核心池大小)和maximumPoolSize(最大池大小)。execute 方法用来接收任务,addWorker 方法用来添加线程执行任务。线程池满了之后,任务将被放入taskQueue(任务队列)。若队列也满了,则通过reject方法来执行拒绝策略。
3. 编写测试程序
3.1 单元测试的重要性
在任何软件开发过程中,编写测试程序对保证代码质量至关重要。它能够帮助开发者快速发现问题、验证功能的正确性以及确保代码能够在修改和扩展后依旧正常工作。对于线程池的实现,由于并发编程的复杂性,测试变得尤为重要。
3.2 测试线程池的正确性和效率
我们需要编写测试用例来确保自己实现的线程池行为正确。下面是一个基本的测试用例:
public class ThreadPoolTest {
public static void main(String args) {
final SimpleThreadPool threadPool = new SimpleThreadPool(2, 4);
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("Task is being executed by " + Thread.currentThread().getName());
try {
Thread.sleep(1000); // 模拟任务执行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// 提交6个任务
for (int i = 0; i < 6; i++) {
threadPool.execute(task);
}
}
}
以上的测试类中创建了2个核心线程和4个最大线程的线程池,并提交了6个任务。正常情况下,核心线程会立即执行两个任务,其他任务排队等候。大家可以动手操作起来!