从源码解读线程(Thread)和线程池(ThreadPoolExecutor)的状态
线程是比进程更加轻量级的调度执行单位,理解线程是理解并发编程的不可或缺的一部分;而生产过程中不可能永远使用裸线程,需要线程池技术,线程池是管理和调度线程的资源池。因为前不久遇到了一个关于线程状态的问题,今天就趁热打铁从源码的层面来谈一谈线程和线程池的状态及状态之间的转移。
线程
JDK中,线程(Thread)定义了6种状态: NEW(新建)、RUNNABLE(可执行)、BLOCKED(阻塞)、WAITING(等待)、TIMED_WAITING(限时等待)、TERMINATED(结束)。
源码如下:
/** * A thread state. A thread can be in one of the following states: * <ul> * <li>{@link #NEW} * A thread that has not yet started is in this state. * </li> * <li>{@link #RUNNABLE} * A thread executing in the Java virtual machine is in this state. * </li> * <li>{@link #BLOCKED} * A thread that is blocked waiting for a monitor lock * is in this state. * </li> * <li>{@link #WAITING} * A thread that is waiting indefinitely for another thread to * perform a particular action is in this state. * </li> * <li>{@link #TIMED_WAITING} * A thread that is waiting for another thread to perform an action * for up to a specified waiting time is in this state. * </li> * <li>{@link #TERMINATED} * A thread that has exited is in this state. * </li> * </ul> * * <p> * A thread can be in only one state at a given point in time. * These states are virtual machine states which do not reflect * any operating system thread states. * * @since 1.5 * @see #getState */ public enum State { /** * Thread state for a thread which has not yet started. */ NEW, /** * Thread state for a runnable thread. A thread in the runnable * state is executing in the Java virtual machine but it may * be waiting for other resources from the operating system * such as processor. */ RUNNABLE, /** * Thread state for a thread blocked waiting for a monitor lock. * A thread in the blocked state is waiting for a monitor lock * to enter a synchronized block/method or * reenter a synchronized block/method after calling * {@link Object#wait() Object.wait}. */ BLOCKED, /** * Thread state for a waiting thread. * A thread is in the waiting state due to calling one of the * following methods: * <ul> * <li>{@link Object#wait() Object.wait} with no timeout</li> * <li>{@link #join() Thread.join} with no timeout</li> * <li>{@link LockSupport#park() LockSupport.park}</li> * </ul> * * <p>A thread in the waiting state is waiting for another thread to * perform a particular action. * * For example, a thread that has called <tt>Object.wait()</tt> * on an object is waiting for another thread to call * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on * that object. A thread that has called <tt>Thread.join()</tt> * is waiting for a specified thread to terminate. */ WAITING, /** * Thread state for a waiting thread with a specified waiting time. * A thread is in the timed waiting state due to calling one of * the following methods with a specified positive waiting time: * <ul> * <li>{@link #sleep Thread.sleep}</li> * <li>{@link Object#wait(long) Object.wait} with timeout</li> * <li>{@link #join(long) Thread.join} with timeout</li> * <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li> * <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li> * </ul> */ TIMED_WAITING, /** * Thread state for a terminated thread. * The thread has completed execution. */ TERMINATED; }
状态说明
线程在一个给定的时间点只能处于下面其中一种状态:
这些状态是虚拟机状态,并不能反映任何操作系统的线程状态。
- NEW:尚未启动的线程处于这个状态。Thread thread = new Thread(new Runnable(){...});处于这个状态。
- RUNNABLE:可运行的线程处于这个状态。对应操作系统中的两种状态:ready和running,也就是说RUNNABLE状态既可以是可运行的,也可以是实际运行中的,有可能正在执行,也有可能没有正在执行。关于这个问题的理解,可以对比想一下,thread.start()调用之后线程会立刻执行吗?
- BLOCKED:阻塞,进入synchronized修饰的方法或者代码块,等待监视器锁的线程处于这个状态。
- WAITING:无限期等待另一个线程执行特定操作的线程处于这种状态。
- TIMED_WAITING:正在等待另一个线程执行某个操作的线程在指定的等待时间内处于这种状态。
- TERMINATED:已经退出的线程处于这个状态。
状态转移
NEW:线程尚未启动的线程状态。当在程序中创建一个线程的时候Thread t = new Thread(Runnable);,线程处于NEW状态。
RUNNABLE:可运行线程的线程状态。处于可运行状态的线程正在Java虚拟机中执行,但它可能正在等待操作系统中的其他资源,比如处理器。也就是说, 这个状态就是可运行也可不运行的状态。注意Runnable ≠ Running。
BLOCKED:进入synchronized修饰的方法或者代码块,等待监视器锁的阻塞线程的线程状态。比如,线程试图通过synchronized去获取监视器锁,但是其他线程已经独占了,那么当前线程就会处于阻塞状态。等到获得了监视器锁之后会再次进入RUNNABLE状态。
WAITING:调用以下方法之一,线程会处于等待状态:
- Object.wait()注意:括号内不带参数;
- Thread.join()注意:扩号内不带参数;
- LockSupport.park();
其实wait()方法有多重形式,可以不带参数,可以带参数,参数表示等待时间(单位ms),如图所示:
“BLOCKED(阻塞状态)”和“WAITING(等待状态)”的区别:阻塞状态在等待获取一个排它锁,这个事件将会在另外一个线程放弃这个锁的时候发生,然后由阻塞状态变为可执行状态;而等待状态则是在等待一段时间,或者等待唤醒动作的发生。
TIMED_WAITING:一个线程调用了以下方法之一(方法需要带具体的等待时间),会处于定时等待状态:
- Thread.sleep(long timeout)
- Object.wait(long timeout)
- Thread.join(long timeout)
- LockSupport.parkNanos()
- LockSupport.parkUntil()
TERMINATED: 该线程已经执行完毕。执行完毕指的是线程正常执行完了run方法之后退出,也可以是遇到了未捕获的异常而退出。
其实这些大部分在源码的注释中可以找到。下面我自己翻译的中文版,不嫌弃的话可以参考:
/** * 线程状态。 一个线程可以处于下列状态之一: * * NEW:尚未启动的线程处于这个状态。 * * RUNNABLE:正在Java虚拟机中执行的线程处于这个状态。 * * BLOCKED:阻塞中,等待监视器锁的线程处于这个状态。 * * WAITING:无限期等待另一个线程执行特定操作的线程处于这种状态。 * * TIMED_WAITING:正在等待另一个线程执行某个操作的线程在指定的等待时间内处于这种状态。 * * TERMINATED:已经退出的线程处于这个状态。 * * 线程在一个给定的时间点只能处于一种状态。 * 这些状态是虚拟机状态,并不能反映任何操作系统的线程状态。 * */ public enum State { /** * 线程尚未启动的线程状态。 */ NEW, /** * 可运行线程的线程状态。处于可运行状态的线程正在Java虚拟机中执行, * 但它可能正在等待操作系统中的其他资源,比如处理器。 */ RUNNABLE, /** * 等待监视器锁的阻塞线程的线程状态。 * 处于阻塞状态的线程正在等待监视器锁进入同步块/方法, * 或者在调用Object.wait后重新进入同步块/方法。 */ BLOCKED, /** * 等待线程的线程状态。 * 调用以下方法之一,线程会处于等待状态: * Object.wait()注意:括号内不带参数; * Thread.join()注意:扩号内不带参数; * LockSupport.park(); * * 处于等待状态的线程正在等待另外一个线程执行特定的操作。 * * 例如,一个调用了object.wait()方法的线程正在等待另外一下线程调用 * object.notify()或者object.notifyAll()方法。注意,这两个object是同一个object。 * 一个调用了Thread.join()方法的线程正在等待一个特定的线程去终止。 */ WAITING, /** * 具有指定等待时间的等待线程的线程状态。 * 一个线程调用了以下方法之一(方法需要带具体的等待时间),会处于定时等待状态: * Thread.sleep(timeout) * Object.wait(timeout) * Thread.join(timeout) * LockSupport.parkNanos() * LockSupport.parkUntil() */ TIMED_WAITING, /** * 终止的线程状态。 * 该线程已经执行完毕。 */ TERMINATED; }
状态转移图如图所示:
线程常用方法说明
类 | 方法名 | 说明 |
Thread | sleep | 线程休眠 |
join | 等待其他线程执行完毕 | |
yield | 放弃已经获取到的CPU资源 | |
currentThread | 获取当前执行的线程 | |
start,run | 启动线程相关 | |
stop,suspend,resume | 已废弃 | |
Object | wait,notify,notifyAll | 让线程暂时休息和唤醒 |
根据《Effective Java》,直接使用wait方法和notify方法就像使用“并发汇编语言”进行编程一样,而concurrent包则提供了更高级的语言。没有理由在新代码中使用wait和notify方法,即使有,也是极少的。如果在维护使用wait和notify方法的代码,务必确保始终是利用标准的模式从while循环内部调用wait方法。一般情况下,应该优先使用notifyAll方法,而不是notify方法。如果使用notify方法,请一定小心,以确保程序的活性。
线程池
在生产环境中,为每个任务分配一个线程是存在缺陷的,例如资源消耗和稳定性等,所以需要使用线程池。
Java类库提供了灵活的线程池,可以调用Executors中的静态工厂方法创建线程池。如
- newFixedThreadPool:固定长度的线程池
- newCachedThreadPool:可缓存的线程池。
不管是newFixedThreadPool还是newCachedThreadPool,底层都是通过ThreadPoolExecutor实现的,本文只谈ThreadPoolExecutor的状态。
在JDK源码中,线程池(ThreadPoolExecutor)定义了五种状态:RUNNING、SHUTDOWN、STOP、TIDYING和TERMINATED。
源码如下:
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;
状态说明
-
RUNNING — 运行状态,可以添加新任务,也可以处理阻塞队列中的任务。
-
SHUTDOWN — 待关闭状态,不再接受新的任务,会继续处理阻塞队列中的任务。
-
STOP — 停止状态,不再接受新的任务,不会执行阻塞队列中的任务,打断正在执行的任务。
-
TIDYING — 整理状态,所有任务都处理完毕,workerCount为0,线程转到该状态将会运行terminated()钩子方法。
-
TERMINATED — 终止状态,terminated()方法执行完毕。
状态转移
* RUNNING -> SHUTDOWN * On invocation of shutdown(), perhaps implicitly in finalize() * (RUNNING or SHUTDOWN) -> 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
-
线程池的初始化状态是RUNNING。换句话说,线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0。
-
当线程池处于RUNNING状态时,调用shutdown()方法,线程池RUNNING状态转为SHUTDOWN状态。
-
当线程池处于RUNNING or SHUTDOWN时,调用shutdownNow()方法时,线程池由(RUNNING or SHUTDOWN )状态转为STOP状态。
-
当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN状态转为TIDYING状态。
-
当线程池处于STOP状态,当线程池中执行的任务为空的时候,线程池有STOP状态转为TIDYING状态。
-
当线程池处于TIDYING状态,当执行完terminated()之后,就会由TIDYING状态转为TERMINATED状态。
状态转移图如图所示:
总结
理解线程和线程池对于我们日常开发或者诊断分析,都是不可或缺的基础。本文从源码分析了线程和线程池的状态和各种方法之间的对应关系,希望对大家有帮助,文中如果有地方不妥还请大家指正。