参考文献: 深入理解Java并发框架AQS系列(一):线程

1. 概述

当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,它要经过 6 种状态:

  • NEW-初始
  • RUNNABLE-可运行
  • BLOCKED-阻塞
  • WAITING-等待
  • TIMED_WAITING-超时等待
  • TERMINATED-结束

为了论述的更为彻底,我们站在操作系统的角度,将 RUNNABLE-可运行 状态拆分为 runnable-就绪状态 及 running-运行状态,故一共7种状态。

我尝试想用一张图把状态流转描述清楚,发现非常困难,由于wait/notify使用的特殊性,会将整个流程图搅得很乱,所以此处我们把状态流转拆分为(非wait方法)及(wait方法)。

2109301-20210217211432784-130378987

1. 新建状态(new)

当程序使用 new 关键字创建了一个线程之后,该线程就处于新建状态,此时仅由 JVM 为其分配内存,并初始化其成员变量的值

2. 就绪状态(runnable)

当线程对象调用了 start() 方法之后,该线程处于就绪状态。Java 虚拟机会为其创建方法调用栈和程序计数器,等待调度运行。

就绪状态表示当前线程已经启动,只要操作系统调度了cpu时间片,即可运行,其本质上还是处于等待;例如3个正常启动且无阻塞的线程,运行在一个2核的计算机上,那么在某一个时刻,一定至少有1个线程处于就绪状态,等待着cpu资源

3. 运行状态(running)

唯一一个正在运行中的状态,且当前线程没有阻塞、休眠、挂起等;处于此状态的线程,通过主动调用Thread.yield()方法,可变为就绪状态

4. 阻塞状态(blocked)

线程被动地处于synchronized的阻塞队列中,没有超时概念、不响应中断

5. 等待状态(waiting)

顾名思义,线程处于主动等待中,且响应中断;当线程主动调用了以下3个方法时,即处于等待状态,等待其他线程的唤起

  • Thread.join()
  • LockSupport.park()
  • Object.wait()

与阻塞状态的区别:

  • 阻塞状态:线程总是被动的处于阻塞状态,当一个线程执行 synchronized 代码块时,它不知道自己马上抢到锁并执行后续逻辑还是会被阻塞
  • 等待状态:线程很清楚自己接下来要处于等待状态,而且这个命令是线程自己发起的,即便何时被唤醒它无法控制

6. 超时等待(timed_waiting)

此状态与waiting状态定义基本一致,只是引入了超时概念;进入timed_waiting的方法如下:

  • Thread.sleep(long)
  • Thread.join(long)
  • LockSupport.parkNanos(long)
  • LockSupport.parkUntil(long)
  • Object.wait(long)

7. 线程死亡(dead)

线程会以下面三种方式结束,结束后就是死亡状态。

  • 正常结束

    run()或 call()方法执行完成,线程正常结束。

  • 异常结束

    线程抛出一个未捕获的 Exception 或 Error。

  • 调用 stop

    直接调用该线程的 stop() 方法来结束该线程—该方法通常容易导致死锁,不推荐使用。

8. 状态流转

初始 -> 就绪

线程调用Thread.start()方法即可进入就绪状态

就绪 -> 运行

操作系统调度,JVM层面无法干预

运行 -> 就绪

分主动、被动2种方式

  • 1、当前线程的cpu时间片用完,被动进入就绪状态
  • 2、主动调用Thread.yield()

运行 -> 阻塞

2种场景可将一个运行状态的线程变为阻塞状态,且都与synchronized相关

  • 场景1:线程因争抢synchronized锁失败,从而进入等待队列时,线程状态置为blocked
  • 场景2:处于Object.wait()的线程在被唤醒后,不会立即去执行后续代码,而且是会重新争抢synchronized锁,争抢失败的即会进入同步队列排序,此时的线程状态同样为blocked

运行 -> 等待

  • 场景1:调用Thread.join()
  • 场景2:调用LockSupport.park(),即挂起线程,且只能挂起当前线程
  • 场景 3:调用 Object.await()

运行 -> 超时等待

  • Thread.sleep(long)
  • Thread.join(long)
  • LockSupport.parkNanos(long)
  • LockSupport.parkUntil(long)

等待/超时等待 -> 阻塞

当执行完Object.wait()/Object.wait(long)后,不会马上进入就绪状态,线程间还要继续争抢同步队列的锁,争抢失败的便会进入阻塞状态;

运行 -> 终止

线程正常执行完毕,结束了run方法后便进入终止状态,无法再被唤起,等待GC回收