1.1.2 Java线程都有哪些状态?其状态是如何切换的?

Java线程在其生命周期中可以处于以下6种状态。

(1)新建(New)状态。

线程在被创建之后、调用start()方法之前的状态称为新建状态。在这个状态下,线程已经被分配了必要的资源,但还没有开始执行。

(2)可运行(Runnable)状态。

在线程调用了Thread.start()方法之后,它的状态被切换为可运行状态。在这个状态下,线程可能正在运行也可能没有运行,这取决于操作系统给线程分配执行时间的方式。可运行状态包括运行(Running)和就绪(Ready)两个状态,但在Java线程状态中,没有明确区分这两个状态,都归为“可运行状态”。

(3)阻塞(Blocked)状态。

当线程试图获取对象锁来进入同步块,但该锁被其他线程持有时,它就会进入阻塞状态。处于阻塞状态的线程会在获得锁之前一直等待。

(4)等待(Waiting)状态。

线程通过调用wait()、join()、park()等方法进入等待状态。处于等待状态的线程需要等待其他线程执行特定操作(例如通知、中断)才能返回到可运行状态。

(5)超时等待(Timed Waiting)状态。

超时等待状态是线程等待另一个线程执行一个(有时间限制的)操作的状态。比如,调用sleep(long)、wait(long)、join(long)等方法,线程会进入超时等待状态。在指定的时间后,线程将自动返回到可运行状态。

(6)终止(Terminated)状态。

当线程执行完毕,或者线程被中断时,线程会进入终止状态。在这个状态下,线程的任务已经完成,不能再次启动。

了解了线程状态后,我们继续了解线程状态的切换,这有助于我们更好地理解多线程程序的运行机制,以及掌握如何正确地控制线程的执行流程,如图1-1所示。

图1-1

在Java线程中,状态的切换通常是由线程的生命周期事件或对线程执行的操作引起的。下面是线程状态切换的常见路径。

(1)从新建状态到可运行状态。

当线程被创建后,它处于新建状态。调用线程对象的start()方法会启动新线程,并使线程进入可运行状态。

Thread t = new Thread(); // 线程处于新建状态
t.start(); // 线程进入可运行状态

(2)从可运行状态到阻塞状态。

当线程试图获取对象锁来进入同步块,但该锁被其他线程持有时,线程会从可运行状态切换到阻塞状态。

synchronized (obj) {
    // 如果其他线程已经持有obj的锁,当前线程将进入阻塞状态
}

(3)从阻塞状态返回到可运行状态。

当线程在阻塞状态下等待的锁变得可用时,线程会再次进入可运行状态。

(4)从可运行状态到等待状态/超时等待状态。

当线程调用wait()、join()、park()等方法时,它可以从可运行状态切换到等待状态。

Object.wait(); // 线程进入等待状态
Thread.join(); // 线程进入等待状态,直到对应的线程结束

当线程调用有时间限制的方法时,它会进入超时等待状态。

Thread.sleep(1000); // 线程进入超时等待状态,在指定时间后自动返回可运行状态
Object.wait(1000); // 线程进入超时等待状态,在指定时间后自动返回可运行状态

(5)从等待状态/超时等待状态返回到可运行状态。

线程从等待状态/超时等待状态返回到可运行状态通常是由于某个条件被满足,例如:

对于调用wait()方法的线程,某个线程调用了相同对象的notify()或notifyAll()方法;

对于调用join()方法的线程,线程执行完毕;

对于sleep(long)或wait(long)等调用的线程,指定的等待时间已经过去。

(6)从可运行状态到终止状态。

当线程的run()方法执行完毕时,线程将会进入终止状态。

public void run() {
    // 线程的工作代码
} // run()方法执行完毕,线程进入终止状态

调用interrupt()方法来请求中断线程也会使线程进入终止状态。

t.interrupt(); // 请求中断线程

以上是线程状态切换的常见路径,理解这些切换对于编写多线程程序是非常重要的。我们在编写多线程程序时,需要考虑线程同步、互斥锁、等待/通知机制等关键问题。