线程的六种状态

程序猿进阶 2024-10-01 10:35:12 阅读 100

优质博文:IT-BLOG-CN

线程的状态在<code>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)

用<code>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.notifyobj.notifyAll,那么A线程就没有办法恢复运行; 如果A线程调用了LockSupport.park(),没有别的线程调用LockSupport.unpark(A),那么A没有办法恢复运行。

六、超时等待状态(TIMED_WAITING)

区别于WAITING,它可以在指定的时间自行返回。

J.U.C中很多与线程相关类,都提供了限时版本和不限时版本的APITIMED_WAITING意味着线程调用了限时版本的API,正在等待时间流逝。当等待时间过去后,线程一样可以恢复运行。如果线程进入了WAITING状态,一定要特定的事件发生才能恢复运行;而处在TIMED_WAITING的线程,如果特定的事件发生或者是时间流逝完毕,都会恢复运行。

运行状态转换为超时等待:

  ☑️ 调用静态方法Thread.sleep(long)

  ☑️ 线程对象调用wait(long)方法

  ☑️ 其他线程调用指定时间的join(long)

  ☑️ LockSupport.parkNanos()

  ☑️ LockSupport.parkUntil()

七、消亡状态

线程执行完毕run方法或call方法,或者抛出没有捕获的ExceptionError异常而结束,线程都会停留在这个状态。这个时候线程只剩下Thread对象了,没有什么用了。

即线程的终止,表示线程已经执行完毕。前面已经说了,已经消亡的线程不能通过start再次唤醒。

需要注意的是:主线成和子线程互不影响,子线程并不会因为主线程结束就结束。



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。