[Java] 线程池状态小总结

98 阅读4分钟

线程池状态小总结

摘要

根据 ThreadPoolExecutor.java 中的描述,线程池共有如下 5 种状态 ⬇️

  • RUNNING
  • SHUTDOWN
  • STOP
  • TIDYING
  • TERMINATED

它们之间的转化关系如下 ⬇️

stateDiagram-v2 
    RUNNING --> SHUTDOWN: On invocation of shutdown()
    SHUTDOWN --> STOP: On invocation of shutdownNow()
    RUNNING --> STOP: On invocation of shutdownNow()
    SHUTDOWN --> TIDYING: When both queue and pool are empty
    STOP --> TIDYING: When pool is empty
    TIDYING --> TERMINATED: When the terminated() hook method has completed

我们在本文中验证一下这些状态之间的转化关系。

正文

根据 ThreadPoolExecutor.java 中的描述,线程池共有以下 5 种状态 ⬇️

image.png

状态解释
RUNNINGAccept new tasks and process queued tasks
SHUTDOWNDon't accept new tasks, but process queued tasks
STOPDon't accept new tasks, don't process queued tasks, and interrupt in-progress tasks
TIDYINGAll tasks have terminated, workerCount is zero, the thread transitioning to state TIDYING will run the terminated() hook method
TERMINATEDterminated() has completed

ThreadPoolExecutor.java 里描述了这 5 种状态之间的转化条件 ⬇️

image.png

按照上图的描述,我画了下图 ⬇️ (我按照自己的理解在图中也画了中文的版本,如需要查看严谨的描述,请参考英文版本的)

stateDiagram-v2
state original {  
    RUNNING --> SHUTDOWN: On invocation of shutdown()
    SHUTDOWN --> STOP: On invocation of shutdownNow()
    RUNNING --> STOP: On invocation of shutdownNow()
    SHUTDOWN --> TIDYING: When both queue and pool are empty
    STOP --> TIDYING: When pool is empty
    TIDYING --> TERMINATED: When the terminated() hook method has completed
}

state 中文版 {
    state "RUNNING" as r
    state "SHUTDOWN" as sh
    state "STOP" as st
    state "TIDYING" as ti
    state "TERMINATED" as te
    r --> sh: 调用 shutdown() 方法时
    sh --> st: 调用 shutdownNow() 方法时
    r --> st: 调用 shutdownNow() 方法时
    sh --> ti: 当 workQueue 字段和 workers 字段都\n变成空(即,size 为 0)时
    st --> ti: 当 workers 字段变成空(即,size 为 0)时
    ti --> te: 当 terminated() 方法调用结束时
}

准备工作

ThreadPoolExecutor.java 里可以找到如下的代码 ⬇️

    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    private static final int COUNT_BITS = Integer.SIZE - 3;
    private static final int COUNT_MASK = (1 << COUNT_BITS) - 1;

    // runState is stored in the high-order bits
    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;

从这些代码(以及 ctl 字段 的注释)可以看出 ctl 里的 value 字段的高 3 位(因为 COUNT_BITS 的值是 29)保存了线程池的状态信息。

状态用十进制展示
RUNNING-1 << 29536870912-536870912
SHUTDOWN0 << 2900
STOP1 << 29536870912536870912
TIDYING2 << 2910737418241073741824
TERMINATED3 << 2916106127361610612736

查看 RUNNING 状态(其对应值为 -1 << 29536870912-536870912)

请将以下代码保存为 CheckRunning.java ⬇️

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CheckRunning {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.execute(() -> System.out.println("Hello, World"));
        executorService.shutdown();
    }
}

借助 Intellij IDEA (Community Edition) 可以在第 7 行打断点,然后输入以下内容就可以验证线程池此时的状态就是 RUNNING(因为下面这个表达式的值是 536870912-536870912,和 RUNNING 对应的值一样)

ThreadPoolExecutor.runStateOf(((ThreadPoolExecutor) executorService).ctl.get())

image.png

查看 SHUTDOWN 状态(其对应值为 00)

在线程池处于 RUNNING 状态时,如果我们对它调用 shundown() 方法,那么它会变为 SHUTDOWN 状态。但考虑到这个状态可能只会持续很短的时间,所以我写了如下的代码,以使得 SHUTDOWN 状态一直持续下去 ⬇️

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CheckShutdown {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        // 下一行代码提交了一个会一直运行的 `Runnable` 对象,所以对线程池调用 shutdown() 方法后,线程池不会从 `SHUTDOWN` 状态变为 `TIDYING` 状态
        executorService.execute(() -> {
            while (true) {
                Thread.yield();
            }
        });
        executorService.shutdown();
        int a = 42; // 这一行的代码没有实际的意义,只是为了方便打断点才写了这一行
    }
}

请将以上代码保存为 CheckShutdown.java。 借助 Intellij IDEA (Community Edition) 可以在第 14 行打断点,然后输入以下内容就可以验证线程池此时的状态就是 SHUTDOWN(因为下面这个表达式的值是 00,和 SHUTDOWN 对应的值一样)

ThreadPoolExecutor.runStateOf(((ThreadPoolExecutor) executorService).ctl.get())

image.png

查看 STOP 状态(其对应值为 1 << 29536870912536870912)

请将以下代码保存为 CheckStop.java ⬇️

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CheckStop {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.shutdownNow();
    }
}

我们可以在 ThreadPoolExecutor.javashutdownNow() 方法里找个合适的位置来打断点,例如下图这个位置 ⬇️

image.png

当运行到断点处,输入以下内容就可以验证线程池此时的状态就是 STOP(因为下面这个表达式的值是 536870912536870912,和 STOP 对应的值一样)

ThreadPoolExecutor.runStateOf(this.ctl.get())

image.png

查看 TIDYING 状态(其对应值为 2 << 2910737418241073741824)

由前文展示的状态图可知,当线程池从 TIDYING 状态转化为 TERMINATED 状态的条件是 terminated() 方法执行完。在 ThreadPoolExecutor.java 里,terminated() 的代码是这样的 ⬇️

    /**
     * Method invoked when the Executor has terminated.  Default
     * implementation does nothing. Note: To properly nest multiple
     * overridings, subclasses should generally invoke
     * {@code super.terminated} within this method.
     */
    protected void terminated() { }

由于 terminated() 里没有任何逻辑,所以当线程池处于 TIDYING 状态时,会立刻转化为 TERMINATED 状态。这样就很难观察到 TIDYING 状态。但也有变通的办法,既然上面这个 terminated() 方法里什么代码都没有,那我们可以定一个 ThreadPoolExecutor 的子类,并在子类中 override terminated() 方法,这样在子类的 terminated() 方法里,就可以打断点进行观察了。具体的代码如下 ⬇️ (请将代码保存为 CheckTidying.java)

import java.util.concurrent.*;

public class CheckTidying {
    public static void main(String[] args) {
        ExecutorService executorService = new MyThreadPoolExecutor(0, Integer.MAX_VALUE,
                60L, TimeUnit.SECONDS,
                new SynchronousQueue<>());
        executorService.shutdown();
    }
}

class MyThreadPoolExecutor extends ThreadPoolExecutor {

    public MyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    @Override
    protected void terminated() {
        super.terminated();
    }
}

借助 Intellij IDEA (Community Edition) 可以在第 20 行打断点,然后输入以下内容就可以验证线程池此时的状态就是 TIDYING(因为下面这个表达式的值是 10737418241073741824,和 TIDYING 对应的值一样)

ThreadPoolExecutor.runStateOf(this.ctl.get())

image.png

查看 TERMINATED 状态(其对应值为 3 << 2916106127361610612736)

请将以下代码保存为 CheckTerminated.java ⬇️

import java.util.concurrent.*;

public class CheckTerminated {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.execute(() -> System.out.println("Hello world"));
        executorService.shutdown();
        while (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
            System.out.println("I am waiting");
        }
        int a = 42; // 这一行的代码没有实际的意义,只是为了方便打断点才写了这一行
    }
}

借助 Intellij IDEA (Community Edition) 可以在第 11 行打断点,然后输入以下内容就可以验证线程池此时的状态就是 TERMINATED(因为下面这个表达式的值是 16106127361610612736,和 TERMINATED 对应的值一样)

ThreadPoolExecutor.runStateOf(((ThreadPoolExecutor) executorService).ctl.get())

image.png

参考资料