03线程的生命周期以及线程状态转换
Java中线程的生命周期
线程的六种状态
Java语言中线程共有六种状态,分别是:
- NEW(初始化状态)尚未启动的线程状态,即线程创建,还未调用start方法
- RUNNABLE(可运行/运行状态)就绪状态(调用start,等待调度)+正在运行
- BLOCKED(阻塞状态)等待监视器锁时,陷入阻塞状态
- WAITING(无时限等待)等待状态的线程正在等待另一线程执行特定的操作(如notify)
- TIMED_WAITING(有时限等待)具有指定等待时间的等待状态
- TERMINATED(终止状态)线程完成执行,终止状态
备注:其中,BLOCKED、WAITING、TIMED_WAITING可以理解为线程导致休眠状态的三种原因。
线程状态转换
其他的线程状态转换看图,单论运行与阻塞状态转换
RUNNABLE与BLOCKED的状态转换
只有一种场景会触发这种转换,就是线程等待synchronized的隐式锁。synchronized修饰的方法、代码块同一时刻只允许一个线程执行,其他线程只能等待,这种情况下,等待的线程就会从RUNNABLE转换到BLOCKED状态。而当等待的线程获得synchronized隐式锁时,就又会从BLOCKED转换到RUNNABLE状态。
问:线程调用阻塞式API时,是否会转换到BLOCKED状态呢?
在操作系统层面,线程是会转换到休眠状态的,但是在JVM层面,Java线程的状态不会发生变化,也就是说Java线程的状态会依然保持RUNNABLE状态。JVM层面并不关心操作系统调度相关的状态,因为在JVM看来,等待CPU使用权(操作系统层面此时处于可执行状态)与等待I/O(操作系统层面此时处于休眠状态)没有区别,都是在等待某个资源,所以都归入了RUNNABLE状态。
从RUNNABLE到TERMINATED状态
线程执行完 run() 方法后,会自动转换到TERMINATED状态。如果执行run()方法的时候异常抛出,也会导致线程终止。需要强制中止线程的运行只能调用interrupt()方法。
stop()和interrupt()方法的主要区别是什么呢?
stop()方法会真的杀死线程,不给线程喘息的机会,如果线程持有ReentrantLock锁,被stop()的线程并不会自动调用ReentrantLock的unlock()去释放锁,那其他线程就再也没机会获得ReentrantLock锁,故stop已被弃用。
而interrupt()方法就温柔多了,interrupt()方法仅仅是通知线程,线程有机会执行一些后续操作,同时也可以无视这个通知。当线程A处于WAITING、TIMED_WAITING状态时,如果其他线程调用线程A的interrupt()方法,会使线程A返回到RUNNABLE状态,同时线程A的代码会触发InterruptedException异常。
那么线程B调用了线程A的interrupt方法,就能终止线程A吗? 有2种情况:
- 线程处于waiting或者time_waiting状态:当线程 A 处于 WAITING、TIMED_WAITING 状态时,如果线程B调用线程 A 的 interrupt() 方法,会使线程 A 返回到 RUNNABLE 状态,同时线程 A 的代码会触发 InterruptedException 异常。转换到 WAITING、TIMED_WAITING 状态的触发条件,都是调用了类似 wait()、join()、sleep() 这样的方法,我们看这些方法的签名,发现都会 throws InterruptedException 这个异常。这个异常的触发条件就是:其他线程调用了该线程的 interrupt() 方法。
有一点需要注意的是,抛出异常之后JVM 的异常处理会清除线程的中断状态,我们需要用Thread.currentThread().interrupt() 重新设置了线程的中断状态。 - 非waiting状态:这种状态下,线程B调用线程A的interrupt方法,线程A通过Thread.currentThread().isInterrupted()能判断到自己已被中断, 然后自己结束。