优质博文:IT-BLOG-CN
线程的状态在Thread.State
这个枚举类型中定义:共有6种状态,可以调用线程Thread
种的getState()
方法获取当前线程状态。
public enum State { /** * 新建状态(New): * 当用new操作符创建一个线程时, 例如new Thread(r),线程还没有开始运行,此时线程处在新建状态。 当一个线程处于新生状态时,程序还没有开始运行线程中的代码 */ NEW, /** * 就绪状态(Runnable) * 一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。 * 当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。 * 当start()方法返回后,线程就处于就绪状态。 */ RUNNABLE, /** * 运行状态(Running) * 当线程获得CPU时间后,它才进入运行状态,真正开始执行run()方法. */ RUNNING, /** * 阻塞状态(Blocked) * 线程运行过程中,可能由于各种原因进入阻塞状态: * 1>线程通过调用sleep方法进入睡眠状态; * 2>线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者; * 3>线程试图得到一个锁,而该锁正被其他线程持有; * 4>线程在等待某个触发条件; * ...... * 所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,这时其他处于就绪状态的线程就可以获得CPU时间,进入运行状态。 */ BLOCKED, /** * 一个正在等待的线程的状态。也称之为等待状态。 * 造成线程等待的原因有三种,分别是调用Object.wait()、join()以及LockSupport.park()方法。处于等待状态的线程,正在等待其他线程去执行一个特定的操作。 * 例如:因为wait()而等待的线程正在等待另一个线程去调用notify()或notifyAll();一个因为join()而等待的线程正在等待另一个线程结束。 */ WAITING, /** * 一个在限定时间内等待的线程的状态。也称之为限时等待状态。 * 造成线程限时等待状态的原因有五种,分别是: * Thread.sleep(long)、Object.wait(long)、join(long)、LockSupport.parkNanos(obj,long)和LockSupport.parkUntil(obj,long)。 */ TIMED_WAITING, /** * 一个完全运行完成的线程的状态。也称之为终止状态、结束状态。 */ TERMINATED; }
一、六种状态转换图
操作系统中,线程被视为轻量级的进程,所以线程状态就是进程的状态,一个线程确实可以被视为一个轻量级进程Lightweight Process, LWP
二、新建状态(NEW)
用new
关键字新建一个线程,这个线程就处于新建状态。
每一个线程,在堆内存中都有一个对应的Thread
对象。Thread t = new Thread()
;当刚刚在堆内存中创建Thread
对象,还没有调用t.start()
方法之前,线程就处在NEW
状态。在这个状态上,线程与普通的java
对象没有什么区别,就仅仅是一个堆内存中的对象。例如:
private void testStateNew() { Thread thread = new Thread(() -> {}); System.out.println(thread.getState()); // 输出 NEW}
三、运行状态(RUNNABLE)
操作系统中的READY
(就绪状态)和RUNNING
(运行状态)两种状态,在Java
中统称为RUNNABLE
。
READY
(就绪状态)
当线程对象调用了start()
方法之后,线程处于就绪状态,就绪意味着该线程可以执行,但具体啥时候执行将取决于JVM
里线程调度器的调度。
【1】不允许对一个线程多次使用start
:通过下面源码分析得知,在start()
内部,有一个threadStatus
变量。如果它不等于0
,调用start()
会直接抛出异常。
// 使用synchronized关键字保证这个方法是线程安全的public synchronized void start() { // threadStatus != 0 表示这个线程已经被启动过或已经结束了 // 如果试图再次启动这个线程,就会抛出IllegalThreadStateException异常 if (threadStatus != 0) throw new IllegalThreadStateException(); // 将这个线程添加到当前线程的线程组中 group.add(this); // 声明一个变量,用于记录线程是否启动成功 boolean started = false; try { // 使用native方法启动这个线程 start0(); // 如果没有抛出异常,那么started被设为true,表示线程启动成功 started = true; } finally { // 在finally语句块中,无论try语句块中的代码是否抛出异常,都会执行 try { // 如果线程没有启动成功,就从线程组中移除这个线程 if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { // 如果在移除线程的过程中发生了异常,我们选择忽略这个异常 } }}
我们在看下ThreadStatus
的取值逻辑:如下源码可知,调用start()
之后线程的状态就!0
了。
// Thread.getState方法源码:public State getState() { // get current thread state return sun.misc.VM.toThreadState(threadStatus);}// sun.misc.VM 源码:// 如果线程的状态值和4做位与操作结果不为0,线程处于RUNNABLE状态。// 如果线程的状态值和1024做位与操作结果不为0,线程处于BLOCKED状态。// 如果线程的状态值和16做位与操作结果不为0,线程处于WAITING状态。// 如果线程的状态值和32做位与操作结果不为0,线程处于TIMED_WAITING状态。// 如果线程的状态值和2做位与操作结果不为0,线程处于TERMINATED状态。// 最后,如果线程的状态值和1做位与操作结果为0,线程处于NEW状态,否则线程处于RUNNABLE状态。public static State toThreadState(int var0) { if ((var0 & 4) != 0) { return State.RUNNABLE; } else if ((var0 & 1024) != 0) { return State.BLOCKED; } else if ((var0 & 16) != 0) { return State.WAITING; } else if ((var0 & 32) != 0) { return State.TIMED_WAITING; } else if ((var0 & 2) != 0) { return State.TERMINATED; } else { return (var0 & 1) == 0 ? State.NEW : State.RUNNABLE; }}
【2】其他状态转换为就绪状态:
☑️ 线程调用start()
,新建状态转化为就绪状态。
☑️ 线程sleep(long)
时间到,等待状态转化为就绪状态。
☑️ 阻塞式IO
操作结果返回,线程变为就绪状态。
☑️ 其他线程调用join()
方法,结束之后转化为就绪状态。
☑️ 线程对象拿到对象锁之后,也会进入就绪状态。
RUNNING
(运行状态)
处于就绪状态的线程获得了CPU
之后,真正开始执行run()
方法的线程执行体时,意味着该线程就已经处于运行状态。需要注意的是,对于单处理器,一个时刻只能有一个线程处于运行状态。对于抢占式策略的系统来说,系统会给每个线程一小段时间处理各自的任务。时间用完之后,系统负责夺回线程占用的资源。下一段时间里,系统会根据一定规则,再次进行调度。
运行状态转变为就绪状态的情形:
☑️ 线程失去处理器资源。线程不一定完整执行的,执行到一半,说不定就被别的线程抢走了。
☑️ 调用yield()
静态方法,提示调度程序,当前线程愿意放弃当前对处理器的使用。这时,当前线程将会被置为就绪状态,和其他线程一样等待调度,这时候根据不同优先级决定的概率,当前线程完全有可能再次抢到处理器资源。
如果线程长时间停留在在这个状态就不正常了,这说明线程运行的时间很长(存在性能问题),或者是线程一直得不得执行的机会(存在线程饥饿的问题)。
四、阻塞状态(BLOCKED)
线程正在等待获取java
对象的监视器(也叫内置锁),即线程正在等待进入由synchronized
保护的方法或者代码块。synchronized
用来保证原子性,任意时刻最多只能由一个线程进入该临界区域,其他线程只能排队等待。
线程取得锁,就会从阻塞状态转变为就绪状态。
五、等待状态(WAITING)
进入该状态表示当前线程需要等待其他线程做出一些的特定的动作(通知或中断)。也就是说,如果不发生特定的事件,那么处在该状态的线程一直等待,不能获取执行的机会。比如:A
线程调用了obj
对象的obj.wait()
方法,如果没有线程调用obj.notify
或obj.notifyAll
,那么A
线程就没有办法恢复运行; 如果A
线程调用了LockSupport.park()
,没有别的线程调用LockSupport.unpark(A)
,那么A
没有办法恢复运行。
六、超时等待状态(TIMED_WAITING)
区别于WAITING
,它可以在指定的时间自行返回。
J.U.C
中很多与线程相关类,都提供了限时版本和不限时版本的API
。TIMED_WAITING
意味着线程调用了限时版本的API
,正在等待时间流逝。当等待时间过去后,线程一样可以恢复运行。如果线程进入了WAITING
状态,一定要特定的事件发生才能恢复运行;而处在TIMED_WAITING
的线程,如果特定的事件发生或者是时间流逝完毕,都会恢复运行。
运行状态转换为超时等待:
☑️ 调用静态方法Thread.sleep(long)
☑️ 线程对象调用wait(long)
方法
☑️ 其他线程调用指定时间的join(long)
☑️ LockSupport.parkNanos()
☑️ LockSupport.parkUntil()
七、消亡状态
线程执行完毕run
方法或call
方法,或者抛出没有捕获的Exception
或Error
异常而结束,线程都会停留在这个状态。这个时候线程只剩下Thread
对象了,没有什么用了。
即线程的终止,表示线程已经执行完毕。前面已经说了,已经消亡的线程不能通过start
再次唤醒。
需要注意的是:主线成和子线程互不影响,子线程并不会因为主线程结束就结束。