线程是比进程更加轻量级的调度执行单位,理解线程是理解并发编程的不可或缺的一部分;而生产过程中不可能永远使用裸线程,需要线程池技术,线程池是管理和调度线程的资源池。因为前不久遇到了一个关于线程状态的问题,今天就趁热打铁从源码的层面来谈一谈线程和线程池的状态及状态之间的转移。
JDK中,线程(Thread)定义了6种状态: NEW(新建)、RUNNABLE(可执行)、BLOCKED(阻塞)、WAITING(等待)、TIMED_WAITING(限时等待)、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方法之后退出,也可以是遇到了未捕获的异常而退出。
其实这些大部分在源码的注释中可以找到。下面我自己翻译的中文版,不嫌弃的话可以参考:
状态转移图如图所示:
在生产环境中,为每个任务分配一个线程是存在缺陷的,例如资源消耗和稳定性等,所以需要使用线程池。
Java类库提供了灵活的线程池,可以调用Executors中的静态工厂方法创建线程池。如
newFixedThreadPool:固定长度的线程池
newCachedThreadPool:可缓存的线程池。
不管是newFixedThreadPool还是newCachedThreadPool,底层都是通过ThreadPoolExecutor实现的,本文只谈ThreadPoolExecutor的状态。
在JDK源码中,线程池(ThreadPoolExecutor)定义了五种状态:RUNNING、SHUTDOWN、STOP、TIDYING和TERMINATED。
RUNNING — 运行状态,可以添加新任务,也可以处理阻塞队列中的任务。
SHUTDOWN — 待关闭状态,不再接受新的任务,会继续处理阻塞队列中的任务。
STOP — 停止状态,不再接受新的任务,不会执行阻塞队列中的任务,打断正在执行的任务。
TIDYING — 整理状态,所有任务都处理完毕,workerCount为0,线程转到该状态将会运行terminated()钩子方法。
TERMINATED — 终止状态,terminated()方法执行完毕。
线程池的初始化状态是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状态。
理解线程和线程池对于我们日常开发或者诊断分析,都是不可或缺的基础。本文从源码分析了线程和线程池的状态和各种方法之间的对应关系,希望对大家有帮助,文中如果有地方不妥还请大家指正。
由于博主也是在攀登的路上,文中可能存在不当之处,欢迎各位多指教! 如果文章对您有用,那么请点个”推荐“,以资鼓励!