线程状态简介

  经典的线程五态模型,有五种状态:创建、就绪、执行、阻塞、终止。
  而 Java 的线程状态分为了六种状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。
  Java 将就绪和执行统一成 RUNNABLE,将阻塞细分为 BLOCKED、WAITING、TIMED_WAITING。

  Java 中我们说的线程状态在代码中的实现其实就是一个变量的值,这个变量是 Thread 类中的变量——threadStatus。

/* Java thread status for tools,
 * initialized to indicate thread 'not yet started'
 */
private volatile int threadStatus = 0;

  因为 threadStatus 是个整型数据,不形象,因此 Thread 将 threadStatus 映射到了一个枚举类型的变量上,所以我们调用 getState() 方法获取就是表示状态的字母,而不是数字了。

public enum State {
	 NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED;
}
public State getState() {
    // get current thread state
    return sun.misc.VM.toThreadState(threadStatus);
}

  因此 Java 中线程的状态的改变其实就是改变了 threadStatus 的值。

JAVA线程状态有哪几种 java中线程的五种状态_Java

创建状态

  线程经典五态模型中的创建状态对应 Java 中的新建状态(New)。新建状态就是在 Java 程序中使用构造方法创建了一个线程对象。

Thread t = new Thread();

  Thread 类源码中的构造方法都会去调用 init() 方法,因此 Thread 类的对象在创建时就会调用 init() 方法进行一些属性的赋值。
  PS:init() 方法没有给 threadStatus 变量赋值,因此此时 threadStatus 是默认值 0,这个值就对应枚举类 State 中的 NEW。

就绪状态和执行状态

  线程经典五态模型中的就绪状态和执行状态对应 Java 中的可运行状态(Runnable)。

就绪状态

  线程对象创建后,只是在 Java 程序中创建了线程,还没有接触到操作系统的层次,只有其他线程调用了该对象的 start() 方法,该线程才会进入到操作系统层次。
  Thread 类的 start() 方法的源码中是调用了一个 native 方法 start0() 方法。

public synchronized void start() {
   if (threadStatus != 0)
        throw new IllegalThreadStateException();
	...
    try {
        start0();
        started = true;
    } 
    ...
}
private native void start0();

  所以在修改 threadStatus 变量的时候,并不是单纯的 threadStatus = XXX;而是调用到了本地的方法。此时,在操作系统内核中,才会创建一个真正的线程出来,调用了 start() 方法之后,线程的状态就变为了 Runnable。该状态的线程位于就绪队列中,等待获取CPU的使用权,以进入 Running 状态。

  小结:

  1. 调用了 start() 方法之后,才会在操作系统中创建一个真正的线程;
  2. start() 方法调用了一个本地方法 start0() 来将线程状态修改为 Runnable;
  3. Java 中的线程和操作系统内核中的线程是一对一的。

运行状态

  运行状态并不属于 Java 给出的六种状态,而是我们根据线程经典五态状态来细分 Runnable 状态,为了区分 CPU 正在执行的线程和等待 CPU 调度的线程。
  CPU 只有一个核心,在某一个时刻,只能运行一个线程,具体执行那个线程,取决于操作系统的调度机制。
  就绪状态的线程获取了 CPU,执行程序代码,此时就是 Running 状态。
  进入到运行状态的线程会有三种结果:

  1. 在 CPU 分配的时间片期间完成任务或者调用了已经不建议的 stop 方法,线程的状态就会变成 Terminated(终止状态),此时线程的生命周期就结束了。
  2. 如果在 CPU 分配的时间片期间没有完成任务或者调用了 yield 方法,线程会重新回到就绪队列中,此时线程的状态变成 Runnable 状态。
  3. 如果在 CPU 分配的时间片期间因为某些原因放弃 CPU 的使用权,而暂时停止运行,那么线程会进入到阻塞状态(对应 Java 线程状态中的 Blocked、Waiting、Timed_Waiting 状态)。

阻塞状态

  阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。

  Java 将阻塞的情况分三种:

  1. 同步阻塞(Blocked):同步阻塞或者IO阻塞。运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池中。
  2. 等待阻塞(Waiting):运行的线程执行 wait()、join() 方法,JVM 会把该线程放入等待池中。( wait 会释放持有的锁) 。
  3. 限时的等待阻塞(Timed_Waiting):运行的线程执行 sleep() 方法或者传递了一个时间参数的 join() 方法、wait() 方法。在一定时间内处于等待阻塞状态,如果时间到了会被唤醒。(注意:sleep是不会释放持有的锁)。

同步阻塞

  对于需要同一个同步锁的多个线程来说,如果某个线程获取了同步锁,保持 Runnable 状态,而其他没有获得锁的线程进入到这个锁对象的同步队列中,由 Runnable 状态变为 Blocked 状态。

JAVA线程状态有哪几种 java中线程的五种状态_等待队列_02


  当获得锁的对象释放了锁之后,这个锁对象的同步队列中的所有线程对象会被唤醒,进入到 Runnable 状态,这些线程会尝试进行抢锁,抢到锁的线程状态保持为 Runnable,没有抢到锁的对象继续回到锁对象的同步队列中,变为 Blocked 状态。

JAVA线程状态有哪几种 java中线程的五种状态_JAVA线程状态有哪几种_03

等待阻塞

  正在运行的线程执行 wait() 方法之后,会释放掉所占有的锁资源,线程状态变为 WAITING,并进入到该锁对象的等待队列中。

JAVA线程状态有哪几种 java中线程的五种状态_线程状态_04


  进入到 Waiting 状态的线程必须由另一个线程调用 notify/notifyAll 方法来通知等待队列中的线程。

  notify 方法是根据某些算法确定通知某一个等待队列中的线程对象进入到同步队列,执行 notify 方法的线程执行结束,释放对象锁,同步队列的线程尝试抢锁。

  notifyAll 方法是通知所有的等待队列中的线程对象,进入到同步队列,执行 notifyAll 方法的线程执行结束,释放对象锁,同步队列的线程尝试抢锁,获得锁的线程保持 Runnable 状态,其他没有获得锁的线程对象则是会进入该锁对象的同步队列中。(注意:抢锁失败之后,会进入到同步队列,而不是回到等待队列)。

  除了使用其他线程调用 notify() 方法来通知等待队列中的线程,线程在调用 wait() 方法时,还可以设置一个时间,等待的时间到达设置的这个时间时,线程就会被通知变为 Runnable 状态。

join() 方法

  和 wait() 方法使线程进入到等待队列还可以使用 join() 方法,join() 方法从名字上来理解就是插队,即让当前执行的线程进入到等待队列中,而调用 join() 方法的线程进入到 Runnable 状态,等到调用 join() 方法的线程执行完毕之后,之前进入到等待队列中的线程才会被通知,转为 Runnable 状态。

  join() 方法源码的有用信息简化之后如下,可以看到 join() 方法是调用了 wait() 方法的。而调用 join() 方法的线程执行完之后,是 JVM 调用了 notifyAll() 方法来通知的。

public final synchronized void join(long millis) throws InterruptedException {
	while (isAlive()) {
	    wait(millis);
	}
}

  虽然 join() 方法调用的是 wait() 方法,但是 join() 方法属于其他阻塞,而不是等待阻塞。

限时的等待阻塞

sleep() 方法

  记住 sleep() 方法不会释放锁即可。

终止状态(Terminated)

  线程执行结束或者因异常退出了 run() 方法,该线程结束生命周期。

问题

  调用阻塞 IO 方法,线程变成什么状态?
  在操作系统层次,线程属于阻塞状态,需要获取到 I/O 资源之后转为就绪状态。
  在 Java 层次来说,是 Runnable 状态,JVM 认为等待 IO 与等待 CPU 是一样的,这是 Java 在设计时这样设计的。