从0开始创建一个性能良好的线程池

818 阅读16分钟

从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();
}

不出所料,提交的任务未被执行:

jpanda-2024-03-09-16.29.27@2x

那么我们如何解决这个问题,我们回头看一下我们的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​方法:

jpanda-2024-03-09-16.39.13@2x

此时结果方才符合预期.

完善线程池

完成线程池的构造函数

前面我们创建线程池时,需要手动提供任务队列的实例,我们可以为其提供默认值,修改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​函数进行验证,结果符合预期:

jpanda-2024-03-09-16.55.23@2x

总结

上面就是从0至1构建一个线程池的完整思路和过程了,事实上如果可以接受使用自旋机制的话,上述的代码将会简单很多,但也会**浪费一些cpu​资源.

这只是一个简单的线程池,事实上,还有更多特性没有实现,比如,队列满了的处理策略,如何获取提交任务的返回结果等等...

但是,基本上线程池的实现思路都和这个简单的线程池差不多,无外乎是功能特性多少的问题**.

因为线程池中涉及到了锁,所以,有机会的话,再写一篇文章,去谈谈一下java​线程,Java21​的虚拟线程以及goroutine​之间的实现原理.

这篇文章就先这样~

image

最后的最后,贴上完整代码

完整代码

  • 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;
        }
    }