线程池的实现原理
关于线程池原理,详情请看 线程池原理
线程池是一个典型的生产者-消费者模型。下图所示为线程池的实现原理:
- 调用方不断向线程池中提交任务; (生产者)
- 线程池中有一组线程,不断地从队列中取任务,(消费者)
- 线程池管理一个任务队列,对 异步任务进行缓冲 (缓冲区)
要实现一个线程池,有几个问题需要考虑:
- 队列设置多长?
- 如果是无界的,调用方不断往队列中方任务,可能导致内存耗尽。如果是有界的,当队列满了之后,调用方如何处理?
- 线程池中的线程个数是固定的,还是动态变化的?
- 每次提交新任务,是放入队列?还是开新线程
- 当没有任务的时候,线程是睡眠一小段时间?还是进入阻塞?如果进入阻塞,如何唤醒?
针对问题4,有3种做法:
- 不使用阻塞队列,只使用一般的线程安全的队列,也无阻塞/唤醒机制。当队列为空时,线程池中的线程只能睡眠一会儿,然后醒来去看队列中有没有新任务到来,如此不断轮询。
- 不使用阻塞队列,但在队列外部,线程池内部实现了阻塞/唤醒机制
- 使用阻塞队列
很显然,做法3最完善,既避免了线程池内部自己实现阻塞/唤醒机制的麻烦,也避免了做法1的睡眠/轮询带来的资源消耗和延迟。
手写线程池
定义一个线程池接口
首先,定义一个线程池接口,用来表示线程池应该具备哪些功能。
一个简单的线程池应该至少具备以下几个功能:
- 添加任务并执行
- 关闭线程池
- 强制关闭线程池
import java.util.List;
public interface ThreadPool {
//提交任务到线程池中
void execute(Runnable task);
//优雅关闭线程
void shutdown();
//立即关闭
List<Runnable> shutdownNow();
}
实现线程的池化管理
import java.util.List;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
public class WorkThread extends Thread {
//传入 threads 方便删除线程
private List<WorkThread> threads;
// 空闲时长
private long keepAliveTime;
private BlockingQueue<Runnable> taskQueue;
public WorkThread(long keepAliveTime,BlockingQueue<Runnable> taskQueue,List<WorkThread> threads){
this.threads = threads;
this.keepAliveTime = keepAliveTime;
this.taskQueue = taskQueue;
}
@Override
public void run() {
long lastActiveTime = System.currentTimeMillis();
Runnable task;
while (!Thread.currentThread().isInterrupted() && !taskQueue.isEmpty()){
try {
// 从任务队列中取出一个任务,如果队列为空,则阻塞等待
task = taskQueue.poll(keepAliveTime, TimeUnit.MILLISECONDS);
if (task != null) {
task.run();
lastActiveTime = System.currentTimeMillis();
} else if (System.currentTimeMillis() - lastActiveTime >= keepAliveTime) {
// 从线程池中移除
threads.remove(this);
System.out.printf("WorkerThread %d exit %n", Thread.currentThread().getId());
break;
}
} catch (InterruptedException e) {
// 从线程池中移除
threads.remove(this);
e.printStackTrace();
// 如果线程被中断,则退出循环
break;
}
}
}
}
在WorkerThread类run方法里,采用taskQueue.poll方法指定等待时长,这里是线程退出的关键。如果超时未获取到任务,则表明当前线程长时间未处理任务,可以正常退出,并从线程池里移除该线程。
自定义线程池的基本参数
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class SimpleThreadPool implements ThreadPool {
//线程池初始化时的线程数量
private int initSize;
// 线程池最大线程数量
private int maxSize;
//线程池核心线程数量
private int coreSize;
// 队列大小
private int queueSize;
// 线程空闲时长
private long keepAliveTime;
//任务队列
private BlockingQueue<Runnable> taskQueue;
//工作线程
private List<WorkThread> threads;
private volatile boolean isShutdown = false;
public SimpleThreadPool(int initSize, int maxSize, int coreSize, int queueSize, long keepAliveTime) {
this.initSize = initSize;
this.maxSize = maxSize;
this.coreSize = coreSize;
this.queueSize = queueSize;
this.keepAliveTime = keepAliveTime;
taskQueue = new LinkedBlockingQueue<>(queueSize);
threads = new ArrayList<>(initSize);
// 初始化方法,创建一定数量的工作线程,并启动它们
for (int i = 0; i < initSize; i++) {
WorkThread workerThread = new WorkThread(keepAliveTime, taskQueue, threads);
workerThread.start();
threads.add(workerThread);
}
}
//将任务添加到队列中
@Override
public void execute(Runnable task) {
if (isShutdown) {
throw new IllegalStateException("ThreadPool is shutdown");
}
// 当线程数量小于核心线程数时,创建新线程
if (threads.size() < coreSize) {
addWorkerThread(task);
} else if (!taskQueue.offer(task)) {
// 当队列已满时,且线程数量小于最大线程数量时,创建新的线程
if (threads.size() < maxSize) {
addWorkerThread(task);
} else {
throw new IllegalStateException("执行任务失败");
}
}
}
// 创建新的线程,并执行任务
private void addWorkerThread(Runnable task) {
WorkThread workerThread = new WorkThread(keepAliveTime, taskQueue, threads);
threads.add(workerThread);
// 任务放入队列
taskQueue.offer(task);
}
//关闭线程池
@Override
public void shutdown() {
// 修改状态
isShutdown = true;
for (WorkThread thread : threads) {
// 中断线程
thread.interrupt();
}
}
@Override
public List<Runnable> shutdownNow() {
// 修改状态
isShutdown = true;
// 清空队列
List<Runnable> remainingTasks = new ArrayList<>();
taskQueue.drainTo(remainingTasks);
// 中断所有线程
for (WorkThread thread : threads) {
thread.interrupt();
}
// 返回未执行任务集合
return remainingTasks;
}
}