本文的主题是讨论线程的状态。主要为操作系统层面的线程状态以及JVM层面上的线程状态之间的联系。并对JVM线程不同状态间的转换的方式进行梳理。

1. 操作系统层面

操作系统层面的线程状态有五种,分别是:New、Ready、Running、Waiting、Terminate。如下图所示:

Pasted image 20240430112145

  • New: 仅是在语⾔层⾯创建了线程对象,还未与操作系统线程关联;
  • Ready: 指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执⾏;
  • Running: 指获取了 CPU 时间⽚运⾏中的状态。当 CPU 时间⽚⽤完,会从运⾏状态转换⾄可运⾏状态,会导致线程的上下⽂切换;
  • Waiting: 如果调⽤了阻塞 API,如 BIO 读写⽂件,这时该线程实际不会⽤到 CPU,会导致线程上下⽂切换,进⼊Waiting状态,等 BIO 操作完毕,会由操作系统唤醒阻塞的线程,转换⾄Ready状态,与Ready的区别是,对Waiting的线程来说只要它们⼀直不唤醒,调度器就⼀直不会考虑调度它们。
  • Terminate: 表示线程已经执行完毕,生命周期已经结束,不会转换为其他状态。

2. JVM层面

JVM层面的线程状态有六种,可以在java.lang.Thread.State源码中看到,分别是:NEWRUNNABLEBLOCKEDWAITINGTIMED_WAITINGTERMINATED。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public enum State {  

/**
* 尚未启动的线程的线程状态
*/
NEW,

/**
* 可运行线程的线程状态。处于可运行状态的线程正在Java虚拟机中执行,但它可能正在等待来自操作系统(如处理器) 的其他资源。
*/
RUNNABLE,

/**
* 等待监视器锁的线程的线程状态。处于阻塞状态的线程正在等待监视器锁进入同步块
* 方法或在调用后重新进入同步块
* 方法 Object. wait.
*/
BLOCKED,

/**
* 等待线程的线程状态。线程由于调用以下方法之一而处于等待状态:
* Object. wait 没有超时
* Thread. join 没有超时
* LockSupport. park
* 处于等待状态的线程正在等待另一个线程执行特定的操作。
* 例如,一个线程调用 Object. wait () 对象正在等待另一个线程调用 Object. notify () 或 Object. notifyAll () 在那个物体上。已经调用的线程 Thread. join () 正在等待指定线程终止。
*/
WAITING,

/**
* 指定等待时间的等待线程的线程状态。线程处于定时等待状态,因为调用了以下方法之一,并指定了正等待时间:
* thread . sleep
* Object. wait 与超时
* Thread. join 与超时
* LockSupport. parkNanos
* LockSupport. parkUntil
*/
TIMED_WAITING,

/**
* 终止线程的线程状态。线程已完成执行。
*/
TERMINATED;
}

3. 两种层面上线程状态的联系

以下为操作系统层面和JVM层面线程状态之间的关系:

Pasted image 20240506151052

忽略New和Terminate状态。由上图可见,JVM层面的Runnable状态包含了操作系统层面的 ReadyRunningWaiting状态。且BLOCKEDTIMED_WAITINGWAITING是JAVA层面对于 WAITING状态的细分。

Waiting状态在JVM层面也可以被认为是RUNNABLE是因为BIO导致的线程阻塞,Java无法区分。

4. 线程状态的转换

状态转换如下图所示:

Pasted image 20240506144615

对于各种状态转换的情况,以下将逐一解释其转换方式:

4.1 NEW –> RUNNABLE

  • t.start()

4.2 RUNNABLE –> TERMINATE

当前线程的所有代码运行结束后,进入TERMINATE。

4.3 RUNNABLE <–> BLOCKED

  • 线程用synchronized(obj)获取对象锁时,若竞争失败,则进入BLOCKED;
  • 持对象锁线程的同步代码块执⾏完毕,会唤醒该对象上所有 BLOCKED 的线程重新竞争,如果其中 t 线程竞争 成功,从BLOCKED转为RUNNABLE ,其它失败的线程仍然 BLOCKED。

4.4 RUNNABLE <–> WAITING

  • 当前线程调用LockSupport.park()时,当前线程会进入WAITING状态。调用LockSupport.unpark()interrupt()目标线程会进入RUNNABLE状态;
  • 当前线程调用t.join()时,当前线程会进入WAITING状态。t线程运行结束或者调用t.interrupt()目标线程会进入RUNNABLE状态;
  • 当前线程调用obj.wait()时,当前线程会进入WAITING状态。调用obj.notify()或者obj.notifyAll()时,目标线程有可能会进入RUNNABLE状态。

调用notify()时,会随机唤醒一个线程,调用notifyAll()时,会唤醒此条件下的所有线程,让他们进行争抢锁资源,若没争抢到,则继续进入WAITING状态。否则,进入RUNNABLE状态。

4.5 RUNNABLE <–> TIMED_WAITING

  • 当前线程调用LockSupport.parkUtil()或者LockSupport.parkNanos()时,当前线程会进入TIMED_WAITING状态。调用LockSupport.unpark()interrupt()目标线程会进入RUNNABLE状态;
  • 当前线程调用t.join(long n)时,当前线程会进入TIMED_WAITING状态。当前线程等待了n毫秒或者t线程运行结束,或者调用了interrupt()时,当前线程会进入RUNNABLE状态;
  • 当前线程调用wait(long n)时,当前线程会进入TIMED_WAITING状态。当前线程等待了n毫秒,当前线程会进入RUNNABLE状态。或者调用了notify()notifyAll()时,当前线程有可能会进入RUNNABLE状态;
  • 当前线程调用Thread.sleep(long n)时,当前线程会进入TIMED_WAITING状态。n毫秒后当前线程会进入RUNNABLE状态;

以上描述图示如下:

Pasted image 20240506155357