从0开始创建一个性能良好的线程池
个人博客: 猫猫侠的博客
首先是为什么我们需要池化技术?池化技术是什么?
在开发中,我们经常会接触到两个池化技术: 线程池,连接池.
那我们为什么需要池化技术呢?
这是因为有些资源的创建和回收是一个相对比较重的操作.
举个例子来说:
在java中我们创建一个新的线程,从代码上不过是一个new Thread().start()而已,但事实上该操作涉及到很多资源和步骤.
比如,通过内核进行cpu调度,创建线程,为该线程创建内存堆栈信息指针之类的操作.
有些时候,我们本身的业务可能都没有创建线程这个操作消耗的资源多.
同理,释放线程也包括了释放内存等操作.
这时,机智的前人就想到,那我能不能这样做呢:
当一个线程执行完时,不调用内核释放该线程,而是将该线程缓存起来,等到下次再使用该线程时,直接用就好了.
于是线程池就出现了,同理连接池也是类似的原因.
那么池化技术又是什么呢?
池化技术本质上涉及到设计模式中的享元模式
将固定不变的数据放在实例本身,而动态变化的数据,作为方法参数传递给实例.
- 对于线程池: 不变的是线程实例,变化的是线程中执行的任务
- 对于连接池: 不变的是链接,变化的是链接中传递的数据
实现一个简单的线程池
只具有最基础能力的简单线程池
前面我们说了对于线程池而言,不变的是线程实例,变化的是线程中执行的任务.
那么思考一下: 在java中创建一个线程有哪些方式呢?
在java中,真正去创建线程的类只有一个,那就是
Thread,其他的,无论是Runnable也好,Callable和Future也好,他们都需要借助于Thread才能实现多线程任务.
因此,我们创建线程池时,需要缓存的线程对象就是Thread对象,我们知道调用Thread#start方法后,实际上执行的是Thread#run方法:
/**
* If this thread was constructed using a separate
* <code>Runnable</code> run object, then that
* <code>Runnable</code> object's <code>run</code> method is called;
* otherwise, this method does nothing and returns.
* <p>
* Subclasses of <code>Thread</code> should override this method.
*
* @see #start()
* @see #stop()
* @see #Thread(ThreadGroup, Runnable, String)
*/
@Override
public void run() {
if (target != null) {
target.run();
}
}
那么从享元模式中共享数据的角度上来看,我们需要想办法让Thread的run方法可以获取并执行线程池中的不同任务.
所以,我们可以写一个WokerThread类,然后重写run方法,该方法可以被动或主动的从线程池中获取需要执行的任务.
-
被动接受任务的特点: 由线程池分发任务给工作线程,因此线程池可以主动感知和控制任务与工作线程的关系.
-
主动接受任务的特点: 由工作线程向线程池申请任务,因此工作线程可以控制自身何时获取任务,以及拒绝接受任务.
当然,代码是人写的,因此,上面只是一个主观上的特点,实际编码时,我们完全可以两者兼顾.
这里我选择由工作线程主动向线程池申请任务 ,并在没有可执行任务时,进行wait状态.
关于
wait状态,我这里选择使用jdk对象自带的wait和notify方法,而不是使用while搞自旋,主要因为while循环本身是在占用cpu时间片的.
既然是由工作线程主动向线程池申请任务,那么工作线程需要可以访问线程池,同时线程池需要提供一个可以获取任务的方法,所以,最简单的代码应该是这样的:
public class WorkThreadPool {
/**
* 任务队列
*/
private final Queue<Runnable> taskQueue;
/**
* 从任务队列中获取任务
*/
public Runnable getTask() {
return this.taskQueue.poll();
}
}
----- 我是分割线
public class WorkThread extends Thread{
private WorkThreadPool workThreadPool;
@Override
public void run() {
Runnable task = workThreadPool.getTask();
task.run();
}
}
然后我们需要考虑修改WorkThread的run方法,因为当前实现只执行了一次任务,而且没有做null检查,我们可以考虑不停的从任务队列中读取任务来执行,如果没有任务可用了,当前线程应该进行wait状态,并等待唤醒.所以修改代码为:
public class WorkerThread extends Thread{
private WorkerThreadPool workerThreadPool;
@Override
public void run() {
// 只要线程没有中断,则不停的执行doRun方法
while (!this.isInterrupted()) {
doRun();
}
}
private void doRun() {
Runnable task=null;
// 不停的获取任务并执行
while ((task=this.workerThreadPool.getTask())!=null){
task.run();
}
// ①线程应进入wait状态
}
}
请注意上述代码中被标记为①的部分,我并没有实现,因为我们必须考虑,要使用哪个对象作为wait和notify的主体.
WorkerThread从WorkerThreadPool拿不到新的任务了,所有WorkerThread进入了空闲状态,此时应该去唤醒一个等待空闲工作线程的线程执行任务.
那么谁会等待空闲线程呢?这个问题我们待会再看,我们先去看一下WorkerThreadPool.
前面我们为WorkerThreadPool定义了一个Queue类型的taskQueue,并为其提供了从队列中读取任务的方法,但我们还缺少一个往任务队列中添加任务的方法.
参考其他线程池的命名,我们也提供一个submit方法:
public WorkerThreadPool submit(Runnable task) {
this.taskQueue.add(task);
// ② 任务队列中有任务,通知所有等待任务的线程
return this;
}
注意未实现的②部分,当有新任务进入队列时,我们应该去通知那些等待任务的空闲线程去执行该任务,和前面未实现的①相关连,我们就可以确认①和②处的wait和notify的主体了.
因为和任务相关,我们将其命名为:
/**
* 用于控制任务队列的锁,每当任务队列中有任务的时候,会调用notify方法
*/
private final Object addTaskLock = new Object();
然后将其放到WorkerThreadPool中,因为在当前的设计中,Worker是可以访问的WorkerThreadPool对象的,所以可以很方便的访问到addTaskLock对象,当然,我们要为addTaskLock提供getter方法.
然后完善①和②的代码:
private void doRun() {
Runnable task=null;
while ((task=this.workerThreadPool.getTask())!=null){
task.run();
}
// ①线程应进入wait状态
synchronized (this.workerThreadPool.getAddTaskLock()){
try {
this.workerThreadPool.getAddTaskLock().wait();
} catch (InterruptedException e) {
this.interrupt();
}
}
}
public WorkerThreadPool submit(Runnable task) {
this.taskQueue.add(task);
// ② 任务队列中有任务,通知所有等待任务的线程
synchronized (this.addTaskLock) {
this.addTaskLock.notify();
}
return this;
}
然后我们将WorkerThread和WorkerThreadPool关联起来,在WorkerThreadPool中提供一个List字段用于存放所有WorkerThread,并提供一个int类型的值作为构造参数来控制WorkerThreadPool中WorkerThread的数量,参考线程池的命名规则,该字段我们就叫corePoolSize吧.
所以,到目前为止,我们WorkerThreadPool的代码如下:
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
public class WorkerThreadPool {
/**
* 线程池中线程的数量
*/
private final int corePoolSize;
/**
* 任务队列
*/
private final Queue<Runnable> taskQueue;
private final List<WorkerThread> workerThreads;
public WorkerThreadPool(int corePoolSize, Queue<Runnable> taskQueue) {
this.corePoolSize = corePoolSize;
this.taskQueue = taskQueue;
this.workerThreads = new ArrayList<>(corePoolSize);
postConstruct();
}
private void postConstruct() {
for (int i = 0; i < corePoolSize; i++) {
WorkerThread workerThread = new WorkerThread(this);
workerThreads.add(workerThread);
workerThread.start();
}
}
/**
* 用于控制任务队列的锁,每当任务队列中有任务的时候,会调用notify方法
*/
private final Object addTaskLock = new Object();
public WorkerThreadPool submit(Runnable task) {
this.taskQueue.add(task);
// ② 任务队列中有任务,通知所有等待任务的线程
synchronized (this.addTaskLock) {
this.addTaskLock.notify();
}
return this;
}
/**
* 从任务队列中获取任务
*/
public Runnable getTask() {
return this.taskQueue.poll();
}
public Object getAddTaskLock() {
return addTaskLock;
}
}
然后我们写一个main方法测试一下该线程池,真的能运行吗?
public static void main(String[] args) throws InterruptedException {
WorkerThreadPool workerThreadPool = new
WorkerThreadPool(3,new ArrayBlockingQueue<>(9));
Set<Integer> set = new CopyOnWriteArraySet<>();
for (int i = 1; i < 10; i++) {
int finalI = i;
set.add(finalI);
workerThreadPool.submit(()->{
System.out.printf("start[%d] at %s%n", finalI, DateTimeFormatter.ofPattern("HH:mm:ss").format(java.time.LocalTime.now()));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
set.remove(finalI);
System.out.printf("end [%d] at %s%n",finalI, DateTimeFormatter.ofPattern("HH:mm:ss").format(java.time.LocalTime.now()));
});
}
while (true){
if (set.isEmpty()){
System.out.println("All tasks have been completed.");
System.exit(0);
}
Thread.sleep(1000);
}
}
上面的main函数将会输出下面内容:
start[3] at 15:26:25
start[1] at 15:26:25
start[2] at 15:26:25
end [3] at 15:26:26
end [2] at 15:26:26
end [1] at 15:26:26
start[5] at 15:26:26
start[4] at 15:26:26
start[6] at 15:26:26
end [4] at 15:26:27
start[7] at 15:26:27
end [5] at 15:26:27
end [6] at 15:26:27
start[8] at 15:26:27
start[9] at 15:26:27
end [7] at 15:26:28
end [8] at 15:26:28
end [9] at 15:26:28
All tasks have been completed.
为线程池添加阻塞停止函数
所以,上面的线程池是可以运行的,但是,从上面的测试我们也发现了,此时的WorkerThreadPool缺少一个停止函数,该函数应该支持:
阻塞直到线程池中的所有任务都执行完毕.
线程池中的所有任务都执行完毕=所有的工作线程都处于空闲状态+任务队列中没有未处理的任务
-
为了判断: 所有的工作线程都处于空闲状态
我们需要
WorkerThread提供一个方法来反馈当前线程的状态,因此我们为WorkerThread新增一个简单的idle字段,并提供一个isIdle方法.因为
idle可能会被主线程和工作线程跨线程访问,所以使用volatile修饰.private volatile boolean idle=true; public boolean isIdle() { return idle; }然后需要在启动任务后和等待任务前分别更新改字段的状态:
private void doRun() { Runnable task=null; while ((task=this.workerThreadPool.getTask())!=null){ this.idle=false; task.run(); } this.idle=true; // ①线程应进入wait状态 // ... 省略 } -
为了判断: 任务队列中没有未处理的任务
我们可以调用在
WorkerThreadPool中调用this.taskQueue.isEmpty()方法
然后还需要为WorkerThreadPool添加一个stopAndAwait函数来提供功能特性:
阻塞直到线程池中的所有任务都执行完毕.
我们来思考一下,当我们调用WorkerThreadPool的stopAndAwait函数时,WorkerThreadPool应该拒绝接受新的任务提交操作,因此要为WorkerThreadPool提供一个状态字段,用于表示当前线程池已经入停止阶段.
/**
* 一个简单的标记,用于标记线程池是否已经关闭,当调用stopAndAwait方法的时候,会将这个标记置为true
* 注意,线程池可能会被多线程调用,所以这个变量需要使用volatile修饰
*/
private volatile boolean stop = false;
public boolean isStop() {
return stop;
}
然后修改WorkerThreadPool的submit方法,当线程池状态为stop时,应拒绝提交新的任务:
public WorkerThreadPool submit(Runnable task) {
if (this.stop) {
throw new IllegalStateException("线程池已经关闭");
}
this.taskQueue.add(task);
...
}
做了上述的几个准备工作之后,我们开始完善我们的stopAndAwait函数:
public void stopAndAwait(){
// 更新线程池状态
this.stop=true;
// 判断任务队列是否为空
// 判断所有工作线程是否处于空闲状态
// 关闭所有工作线程
this.workerThreads.forEach(WorkerThread::interrupt);
}
首先是判断任务队列是否为空的部分.理论上我们只需要使用while循环做自旋判断就可以了:
while (true){
if (this.taskQueue.isEmpty()){
break;
}
}
工作线程状态判断也是一样:
while (this.workerThreads.stream().anyMatch(wt->!wt.isIdle())){
}
然后我们修改前面用于测试的main方法:
public static void main(String[] args) throws InterruptedException {
WorkerThreadPool workerThreadPool = new
WorkerThreadPool(3,new ArrayBlockingQueue<>(9));
Set<Integer> set = new CopyOnWriteArraySet<>();
for (int i = 1; i < 10; i++) {
int finalI = i;
set.add(finalI);
workerThreadPool.submit(()->{
System.out.printf("start[%d] at %s%n", finalI, DateTimeFormatter.ofPattern("HH:mm:ss").format(java.time.LocalTime.now()));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
set.remove(finalI);
System.out.printf("end [%d] at %s%n",finalI, DateTimeFormatter.ofPattern("HH:mm:ss").format(java.time.LocalTime.now()));
});
}
while (true){
if (set.isEmpty()){
System.out.println("All tasks have been completed.");
System.exit(0);
}
Thread.sleep(1000);
}
}
可以正常等待任务全部执行完毕,并停止.
使用notify取代自旋
但是,前面说过了,自旋时依然会占用cpu资源,所以我们考虑使用notify机制来取代前面的自旋操作.
优化:所有的工作线程都处于空闲状态
我们先来优化判断: 所有的工作线程都处于空闲状态,我们需要一个object,当线程空闲时,调用该object的notify方法,唤醒所有等待该object的线程,因此,我们在WorkerThreadPool中新增一个字段及其getter方法:
/**
* 用于控制出现空闲工作线程的锁,当工作线程空闲的时候,会调用notify方法
*/
private final Object someWorkerThreadIsIdleLock = new Object();
public Object getSomeWorkerThreadIsIdleLock() {
return someWorkerThreadIsIdleLock;
}
然后修改:
-
WorkerThreadPool#stopAndAwait()中// 判断所有工作线程是否处于空闲状态部分// 判断所有工作线程是否处于空闲状态 while (this.workerThreads.stream().anyMatch(wt->!wt.isIdle())){ synchronized (this.someWorkerThreadIsIdleLock){ try { this.someWorkerThreadIsIdleLock.wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } } } -
WorkerThread#doRun中// ①线程应进入wait状态部分private void doRun() { Runnable task=null; while ((task=this.workerThreadPool.getTask())!=null){ this.idle=false; task.run(); } this.idle=true; // ①线程应进入wait状态 synchronized (this.workerThreadPool.getSomeWorkerThreadIsIdleLock()){ this.workerThreadPool.getSomeWorkerThreadIsIdleLock().notify(); } synchronized (this.workerThreadPool.getAddTaskLock()){ try { this.workerThreadPool.getAddTaskLock().wait(); } catch (InterruptedException e) { this.interrupt(); } } }
再次执行刚才的main方法,测试正确,符合预期.
优化:任务队列中没有未处理的任务
和前面差不多,我们创建一个taskQueueMaybeEmptyLock来作为任务队列为空的状态通知锁,理论上我们可以复用之前创建的addTaskLock,因为在当前的设计中stopAndAwait方法被调用后,任务队列不会有新的任务了,但是因为WorkerThread也依赖于addTaskLock唤醒,并尝试执行任务,所以复用addTaskLock锁,可能会导致WorkerThread被多次无效唤醒.
/**
* 用于控制任务队列是否为空的锁,当任务队列可能为空的时候,会调用notify方法
*/
private final Object taskQueueMaybeEmpty = new Object();
public Object getTaskQueueMaybeEmpty() {
return taskQueueMaybeEmpty;
}
然后再修改WorkerThread#doRun中// ①线程应进入wait状态部分:
private void doRun() {
Runnable task=null;
while ((task=this.workerThreadPool.getTask())!=null){
this.idle=false;
task.run();
}
this.idle=true;
// ①线程应进入wait状态
synchronized (this.workerThreadPool.getSomeWorkerThreadIsIdleLock()){
this.workerThreadPool.getSomeWorkerThreadIsIdleLock().notify();
}
synchronized (this.workerThreadPool.getTaskQueueMaybeEmptyLock()){
this.workerThreadPool.getTaskQueueMaybeEmptyLock().notify();
}
synchronized (this.workerThreadPool.getAddTaskLock()){
try {
this.workerThreadPool.getAddTaskLock().wait();
} catch (InterruptedException e) {
this.interrupt();
}
}
}
以及修改WorkerThreadPool#stopAndAwait()中判断任务队列是否为空的空body:
// 判断任务队列是否为空
while (true){
if (this.taskQueue.isEmpty()){
break;
}
synchronized (this.taskQueueMaybeEmptyLock){
try {
this.taskQueueMaybeEmptyLock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
// 判断所有工作线程是否处于空闲状态
然后再运行前面写的main方法,函数依然能够正确执行.
解决容易忽略的问题
但是,到这里我们的线程池真的没问题吗?再仔细看一眼WorkerThread#doRun:
private void doRun() {
Runnable task=null;
while ((task=this.workerThreadPool.getTask())!=null){
this.idle=false;
task.run();
}
this.idle=true;
// ①线程应进入wait状态
synchronized (this.workerThreadPool.getSomeWorkerThreadIsIdleLock()){
this.workerThreadPool.getSomeWorkerThreadIsIdleLock().notify();
}
synchronized (this.workerThreadPool.getTaskQueueMaybeEmptyLock()){
this.workerThreadPool.getTaskQueueMaybeEmptyLock().notify();
}
synchronized (this.workerThreadPool.getAddTaskLock()){
try {
this.workerThreadPool.getAddTaskLock().wait();
} catch (InterruptedException e) {
this.interrupt();
}
}
}
思考一下,有没有这样一种可能呢?
代码执行到第7至第14行之间时,一个新的任务被提交到线程池,并调用了
addTaskLock的notify方法,但此时当前工作线程并没有开始等待addTaskLock,因此不会被唤醒,这种情况下,我们的线程池将会得到一个不会被执行的任务.
我们写一个用例测试一下,在此之前,我们临时修改doRun()方法,在第15行添加一些内容,并添加一条输出:
synchronized (this.workerThreadPool.getTaskQueueMaybeEmptyLock()){
this.workerThreadPool.getTaskQueueMaybeEmptyLock().notify();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
this.interrupt();
}
synchronized (this.workerThreadPool.getAddTaskLock()){
try {
System.out.println("线程"+this.getName()+"进入等待状态");
this.workerThreadPool.getAddTaskLock().wait();
} catch (InterruptedException e) {
this.interrupt();
}
}
}
然后新建一个main函数:
public static void main(String[] args) throws InterruptedException {
WorkerThreadPool workerThreadPool = new
WorkerThreadPool(1,new ArrayBlockingQueue<>(10));
// 此时核心线程池已经启动了
Thread.sleep(1000);
// 此时工作线程运行到Sleep处
workerThreadPool.submit(()->{
System.out.println("never print this line");
});
workerThreadPool.stopAndAwait();
}
不出所料,提交的任务未被执行:
那么我们如何解决这个问题,我们回头看一下我们的doRun方法最后的部分,我们在拿到addTaskLock之后不要立即进入wait状态,而是先判断是否还有没执行的任务,如果有的话就不进入wait状态,因此,我们需要在WorkerThreadPool中添加一个方法needWait来验证工作线程是否需要进入休眠:
public boolean needWait(WorkerThread workerThread) {
return this.taskQueue.isEmpty();
}
然后修改doRun方法最后的部分,加上此处的验证:
synchronized (this.workerThreadPool.getAddTaskLock()){
try {
// 既然此时拿到了addTaskLock,那么我就可以在进入wait之前,先通知一下WorkerThreadPool
// 并由WorkerThreadPool来告知我是否需要进入wait状态
if (!this.workerThreadPool.needWait(this)){
return;
}
System.out.println("线程"+this.getName()+"进入等待状态");
this.workerThreadPool.getAddTaskLock().wait();
} catch (InterruptedException e) {
this.interrupt();
}
}
然后再次运行刚才的main方法:
此时结果方才符合预期.
完善线程池
完成线程池的构造函数
前面我们创建线程池时,需要手动提供任务队列的实例,我们可以为其提供默认值,修改WorkerThreadPool,添加一个字段并在构造函数中初始化数据:
/**
* 任务队列可以容纳的任务数量
*/
private final int taskQueueSize;
public WorkerThreadPool(int corePoolSize, int taskQueueSize) {
this.corePoolSize = corePoolSize;
this.taskQueueSize = taskQueueSize;
this.taskQueue = new ArrayBlockingQueue<>(taskQueueSize);
this.workerThreads = new ArrayList<>(corePoolSize);
postConstruct();
}
当任务队列满时,阻塞提交操作
WorkerThreadPool中的任务队列达到最大值时,将会抛出IllegalStateException异常,其实我们可以捕获该异常,做一些其他的操作,比如提供一个策略类,执行拒绝策略,交给主线程执行,等待可用线程 都可以.
这里我们简单一些,直接等待可用线程就可以了,所以修改submit方法:
public WorkerThreadPool submit(Runnable task) {
if (this.stop) {
throw new IllegalStateException("线程池已经关闭");
}
try {
this.taskQueue.add(task);
} catch (IllegalStateException e) {
// 队列满了,等待空闲线程,当然此处可以配置策略,比如,扩容队列,或者丢弃任务,或者交给调用者处理
synchronized (this.someWorkerThreadIsIdleLock) {
try {
this.someWorkerThreadIsIdleLock.wait();
} catch (InterruptedException interruptedException) {
throw new RuntimeException(interruptedException);
}
}
submit(task);
}
// ② 任务队列中有任务,通知所有等待任务的线程
synchronized (this.addTaskLock) {
this.addTaskLock.notify();
}
return this;
}
再次执行最开始的main函数进行验证,结果符合预期:
总结
上面就是从0至1构建一个线程池的完整思路和过程了,事实上如果可以接受使用自旋机制的话,上述的代码将会简单很多,但也会**浪费一些cpu资源.
这只是一个简单的线程池,事实上,还有更多特性没有实现,比如,队列满了的处理策略,如何获取提交任务的返回结果等等...
但是,基本上线程池的实现思路都和这个简单的线程池差不多,无外乎是功能特性多少的问题**.
因为线程池中涉及到了锁,所以,有机会的话,再写一篇文章,去谈谈一下java线程,Java21的虚拟线程以及goroutine之间的实现原理.
这篇文章就先这样~
最后的最后,贴上完整代码
完整代码
-
WorkerThread
public class WorkerThread extends Thread { private volatile boolean idle = true; private final WorkerThreadPool workerThreadPool; public WorkerThread(WorkerThreadPool workerThreadPool) { this.workerThreadPool = workerThreadPool; } @Override public void run() { while (!this.isInterrupted()) { doRun(); } } private void doRun() { Runnable task = null; while ((task = this.workerThreadPool.getTask()) != null) { this.idle = false; task.run(); } this.idle = true; // ①线程应进入wait状态 synchronized (this.workerThreadPool.getSomeWorkerThreadIsIdleLock()) { this.workerThreadPool.getSomeWorkerThreadIsIdleLock().notify(); } synchronized (this.workerThreadPool.getTaskQueueMaybeEmptyLock()) { this.workerThreadPool.getTaskQueueMaybeEmptyLock().notify(); } try { Thread.sleep(3000); } catch (InterruptedException e) { this.interrupt(); } synchronized (this.workerThreadPool.getAddTaskLock()) { try { // 既然此时拿到了addTaskLock,那么我就可以在进入wait之前,先通知一下WorkerThreadPool // 并由WorkerThreadPool来告知我是否需要进入wait状态 if (!this.workerThreadPool.needWait(this)) { return; } System.out.println("线程" + this.getName() + "进入等待状态"); this.workerThreadPool.getAddTaskLock().wait(); } catch (InterruptedException e) { this.interrupt(); } } } public boolean isIdle() { return idle; } } -
WorkerThreadPool
import java.util.ArrayList; import java.util.List; import java.util.Queue; import java.util.concurrent.ArrayBlockingQueue; public class WorkerThreadPool { /** * 线程池中线程的数量 */ private final int corePoolSize; /** * 任务队列可以容纳的任务数量 */ private final int taskQueueSize; /** * 一个简单的标记,用于标记线程池是否已经关闭,当调用stopAndAwait方法的时候,会将这个标记置为true * 注意,线程池可能会被多线程调用,所以这个变量需要使用volatile修饰 */ private volatile boolean stop = false; /** * 任务队列 */ private final Queue<Runnable> taskQueue; private final List<WorkerThread> workerThreads; /** * 用于控制任务队列的锁,每当任务队列中有任务的时候,会调用notify方法 */ private final Object addTaskLock = new Object(); /** * 用于控制出现空闲工作线程的锁,当工作线程空闲的时候,会调用notify方法 */ private final Object someWorkerThreadIsIdleLock = new Object(); /** * 用于控制任务队列是否为空的锁,当任务队列可能为空的时候,会调用notify方法 */ private final Object taskQueueMaybeEmptyLock = new Object(); public WorkerThreadPool(int corePoolSize, int taskQueueSize) { this.corePoolSize = corePoolSize; this.taskQueueSize = taskQueueSize; this.taskQueue = new ArrayBlockingQueue<>(taskQueueSize); this.workerThreads = new ArrayList<>(corePoolSize); postConstruct(); } private void postConstruct() { for (int i = 0; i < corePoolSize; i++) { WorkerThread workerThread = new WorkerThread(this); workerThreads.add(workerThread); workerThread.start(); } } public WorkerThreadPool submit(Runnable task) { if (this.stop) { throw new IllegalStateException("线程池已经关闭"); } try { this.taskQueue.add(task); } catch (IllegalStateException e) { // 队列满了,等待空闲线程,当然此处可以配置策略,比如,扩容队列,或者丢弃任务,或者交给调用者处理 synchronized (this.someWorkerThreadIsIdleLock) { try { this.someWorkerThreadIsIdleLock.wait(); } catch (InterruptedException interruptedException) { throw new RuntimeException(interruptedException); } } submit(task); } // ② 任务队列中有任务,通知所有等待任务的线程 synchronized (this.addTaskLock) { this.addTaskLock.notify(); } return this; } public void stopAndAwait() { // 更新线程池状态 this.stop = true; // 判断任务队列是否为空 while (!this.taskQueue.isEmpty()) { synchronized (this.taskQueueMaybeEmptyLock) { try { this.taskQueueMaybeEmptyLock.wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } } } // 判断所有工作线程是否处于空闲状态 while (this.workerThreads.stream().anyMatch(wt -> !wt.isIdle())) { synchronized (this.someWorkerThreadIsIdleLock) { try { this.someWorkerThreadIsIdleLock.wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } } } // 关闭所有工作线程 this.workerThreads.forEach(WorkerThread::interrupt); } public boolean needWait(WorkerThread workerThread) { return this.taskQueue.isEmpty(); } public boolean isStop() { return stop; } public Object getSomeWorkerThreadIsIdleLock() { return someWorkerThreadIsIdleLock; } public Object getTaskQueueMaybeEmptyLock() { return taskQueueMaybeEmptyLock; } /** * 从任务队列中获取任务 */ public Runnable getTask() { return this.taskQueue.poll(); } public Object getAddTaskLock() { return addTaskLock; } }