优秀的编程知识分享平台

网站首页 > 技术文章 正文

面试官:说说Java线程的生命周期_线程在生命周期中要经历5种状态

nanyue 2025-02-19 13:16:28 技术文章 4 ℃
在编程中,很多程序都是有生命周期的,比如熟悉的spring bean。线程也不例外。线程其实是操作系统的概念,很多编程语言对操作系统的线程状态进行了封装了,有了自己的差异性,不过,基本上都是雷同的。

在并发编程中,线程的状态至关重要。刚接触线程的时候,线程的各种状态及其状态转换,会让人感觉很乱,一定要区分操作系统的线程状态和Java语言的线程状态。Java语言对于操作系统的线程做了包装,底层还是操作系统的线程。本节讲解操作系统通用线程状态和Java语言的线程状态。

1.操作系统的通用线程状态

操作系统的通用线程状态,总共有5个状态,初始状态、可运行状态、运行状态、休眠状态和终止状态。如下图所示:



1.初始状态

线程已创建,但是不允许分配CPU执行。这里是属于编程语言层面的,操作系统还没创建线程。

2.可运行状态

指线程可以分配CPU去执行。操作系统已经创建了线程。

3.运行状态

处在可运行状态的线程被操作系统分配CPU时间片后,线程就从可运行状态转为可运行状态。

4.休眠状态

运行状态的线程调用阻塞的API(比如阻塞IO)或者等待某个事件或条件,线程就会转换为休眠状态。同时释放CPU使用权,注意休眠状态的线程永远没有机会获得CPU的使用权,也就不会执行,只有当等待的事件或条件满足了,线程会从休眠状态转换到可运行状态。

5.终止状态

线程执行完毕或者出现异常就会进入终止状态。终止状态表明线程的生命周期结束,不会转为其他的状态了。

2.Java语言的线程状态

在Java中,总共有六种状态:

  1. NEW(初始化状态)R
  2. RUNNABLE(可运行 / 运行状态)
  3. BLOCKED(阻塞状态)
  4. WAITING(无时限等待)
  5. TIME_WAITING(有时限等待)
  6. TERMINATED(终止状态)

可以看下Java源码,在java.lang.Thread.State中就有定义。通过如下图做下对比:



首先,Java把操作系统可运行和运行状态统一合并到RUNNABLE状态。将休眠状态细分为三种,
BLOCKED/WAITING/TIMED_WAITING,这三种状态不会获取的CPU的使用权。

其实Java的线程状态转换,除了了线程的生死,我们最需要关注的是RUNNABLE和休眠状态之间的转换即可。这里总结了一张图:


2.1 NEW

如下代码:

public class ThreadState {

    public static void main(String[] args) {
        Thread t = new Thread(()-> System.out.println("hello world"));
        System.out.println(t.getState());
    }
}

运行后,在控制台上输出:NEW,通过Thread new出来一个线程时,该线程就是NEW状态。

2.2 从NEW到RUNNABLE

我们创建线程的两种方式,分别是继承Thread和实现Runnable接口,当我们调用start方法时,线程就从NEW状态转换为RUNNABLE状态。

2.3 RUNNABLE与BLOCKED

前面讲过运行状态的线程调用阻塞的API(比如阻塞IO)或者等待某个事件或条件,线程就会转换为休眠状态,这个转换时操作系统的线程状态,而Java的线程状态不会改变,还是RUNNABLE状态。JVM并不关心操作系统调度的状态。在JVM看来,等待CPU使用权(操作系统里是处在可执行状态)与等待I/O(操作系统是处在休眠状态),都是等待某个资源,所以都归入了RUNNABLE 状态

当且仅当线程等待synchronized的隐式锁,线程才会从RUNNABLE转换为BLOCKED状态。当获得了synchronized的隐式锁就会从BLOCKED转换为RUNNABLE状态。

2.4 RUNNABLE与WAITING状态转换

从 RUNNABLE到WAITING的状态转换,有如下三种情况:

  1. 获得synchronized隐式锁的线程,调用无参数Object.wait()时,线程会从 RUNNABLE到WAITING状态转换。
  2. 调用无参数的Thread.join()方法。当线程t,调用t.join()方法时,执行这个语句的线程会等待t执行完毕,此时该线程从 RUNNABLE转换为WAITING状态,当t执行完毕后,该线程会从WAITING转换为RUNNABLE。
  3. 调用LockSupport.park()。当调用并发包中的LockSupport.park()方法时,会从 RUNNABLE转换为WAITING状态。当调用LockSupport.unpark(Thread thread) ,线程又会从WAITING转换为RUNNABLE。

2.5.RUNNABLE 与 TIMED_WAITING 的状态转换

当调用带有参数的阻塞方法时,就会从RUNNABLE 转换为 TIMED_WAITING状态。当被唤醒或超时时间到就会从TIMED_WAITING进入RUNNABLE状态。总共有5种情况:

  1. Thread.sleep(long millis) ;
  2. Object.wait(long timeout)
  3. 带超时参数的 Thread.join(long millis) 方法;
  4. 带超时参数的 LockSupport.parkNanos(Object blocker, long deadline) 方法;
  5. 带超时参数的 LockSupport.parkUntil(long deadline) 方法。

2.5 从 RUNNABLE 到 TERMINATED 状态

有如下情况:

  1. 线程执行完毕。
  2. 线程执行run方法,抛出异常。
  3. 主动中断。调用Thread.stop()或者Thread.interrupt()。其中Thread.stop(),已经被标记为@Deprecated,已经不建议使用,因为太暴力,在后续的章节会做详解讲解。

3. 如何查看线程的状态

3.1 通过Thread.getState()查看

在程序中,可以通过调用Thread.getState()查看线程的状态。比如如下代码:

public class ThreadState {

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()-> System.out.println("hello world"));
        t.start();
        Thread.sleep(10000);
        System.out.println(t.getState());
    }
}


3.2 通过jstack命令

如上程序,我们通过jps命令或者执行的pid,然后通过命令:jstack pid,可以看到如下输出:

"main" #1 prio=5 os_prio=31 tid=0x00007fd91a003800 nid=0x1803 waiting on condition [0x00007000053af000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
	at java.lang.Thread.sleep(Native Method)
	at net.zhifou.concurrent.base.ThreadState.main(ThreadState.java:11)

jstack是很强大的命令行工具,不但可以查看线程的状态,还可以查看调用栈、锁等信息,后续章节将做详细讲解。

3.4 其他工具

  1. Java VisualVM是java提供的可视化工具,可以通过可视化的方式,展现出java的线程栈信息。
  2. Arthas(阿尔萨斯),是Alibaba开源的Java诊断工具,很好用。这里不做详解,可以从官网获取。
最近发表
标签列表