线程的六种状态

优质博文:IT-BLOG-CN

线程的状态在Thread.State这个枚举类型中定义:共有6种状态,可以调用线程Thread种的getState()方法获取当前线程状态。

java 复制代码
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对象没有什么区别,就仅仅是一个堆内存中的对象。例如:

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()会直接抛出异常。

java 复制代码
// 使用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了。

java 复制代码
// 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再次唤醒。

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

相关推荐
丁总学Java2 分钟前
如何使用 maxwell 同步到 redis?
数据库·redis·缓存
爱吃南瓜的北瓜9 分钟前
Redis的Key的过期策略是怎样实现的?
数据库·redis·bootstrap
万物得其道者成10 分钟前
React Zustand状态管理库的使用
开发语言·javascript·ecmascript
学步_技术16 分钟前
Python编码系列—Python抽象工厂模式:构建复杂对象家族的蓝图
开发语言·python·抽象工厂模式
一心只为学24 分钟前
Oracle密码过期问题,设置永不过期
数据库·oracle
【D'accumulation】29 分钟前
典型的MVC设计模式:使用JSP和JavaBean相结合的方式来动态生成网页内容典型的MVC设计模式
java·设计模式·mvc
小光学长33 分钟前
基于vue框架的宠物销售管理系统3m9h3(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。
数据库
wn53139 分钟前
【Go - 类型断言】
服务器·开发语言·后端·golang
试行44 分钟前
Android实现自定义下拉列表绑定数据
android·java
茜茜西西CeCe1 小时前
移动技术开发:简单计算器界面
java·gitee·安卓·android-studio·移动技术开发·原生安卓开发