年后上班第一天,今天学习线程的生命周期,线程是操作系统里的一个概念,Java语言的线程本质上就是操作系统的线程,它们是一一对应的。

通用的线程生命周期

通用的线程生命周期基本上就是五种形态:初始状态、可运行状态、运行状态、休眠状态、终止状态。

Java线程生命周期_java

  • 初始状态:指的是线程已经被创建,但是话不允许分配CPU执行。这里的创建,仅仅是编程语言层面的创建,在操作系统层面,真正的线程还没有被创建。
  • 可运行状态:指的是线程可以分配CPU执行。在这种状态下,真正的操作系统线程已经被成功创建了,所以可以分配 CPU 执行。
  • 运行状态:当有空闲的CPU时,操作系统会将其分配给一个处于可运行状态的线程,被分配到CPU的线程的状态就是运行状态。
  • 休眠状态:运行状态的线程如果调用一个阻塞的 API(例如以阻塞方式读文件)或者等待某个事件(例如条件变量),那么线程的状态就会转换到休眠状态,同时释放CPU使用权,休眠状态的线程永远没有机会获得CPU使用权。当等待的事件出现了,线程就会从休眠状态转换到可运行状态。
  • 终止状态:线程执行完成或者出现异常进入终止状态,进入终止状态就不能切换到其他状态,意味着线程的生命周期结束。

Java中线程的生命周期

Java中线程有六种状态:

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

在操作系统层面BLOCKED、WAITING、TIME_WAITING其实是一种状态,即休眠状态,Java线程处于这三种状态就会释放CPU。

重点看几种状态的转换:

RUNNABLE与BLOCKED的状态转换

只有一种场景会从RUNABLE转换为BLOCKED,就是线程等待synchronized的隐式锁。synchronized 修饰的方法、代码块同一时刻只允许一个线程执行,其他线程只能等待,这种情况下,等待的线程就会从 RUNNABLE 转换到 BLOCKED 状态。而当等待的线程获得 synchronized 隐式锁时,就又会从 BLOCKED 转换到 RUNNABLE 状态。

线程调用阻塞式API式,在操作系统层面,线程会转换到休眠状态,在JVM层面,Java的线程状态不会发生改变,JVM不关心操作系统调度相关的状态,在JVM看来,等待CPU的使用权和等待I/O没有区别,都是等待某个资源,归入了RUNNABLE状态。

RUNNABLE与WAITING状态的转换

三种常见会触发这种转换:

  • 获得synchronized隐式锁的线程,调用无参数的Object.wait()方法。
  • 调用无参数的Thread.join()方法,例如有一个线程对象 thread A,当调用A.join() 的时候,执行这条语句的线程会等待 thread A 执行完,而等待中的这个线程,其状态会从 RUNNABLE 转换到 WAITING。当线程 thread A 执行完,原来等待它的线程又会从 WAITING 状态转换到 RUNNABLE。
  • 调用LockSupport.park()方法。调用LockSupport.park()方法,当前线程会阻塞,线程的状态从RUNNABLE转换为WAITING。调用LockSupport.unpark(Thread thread) 可唤醒目标线程,目标线程的状态又会从 WAITING 状态转换到 RUNNABLE。
RUNNABLE与TIMED_WAITING状态的转换

有五种场景会触发这种转换:

  • 调用带超时参数的Thread.sleep(long millis)方法;
  • 获得synchronized隐式锁的线程,调用带超时参数的Object.wait(long timeout)方法;
  • 调用带超时参数的Thread.join(long millis) 方法;
  • 调用带超时参数的 LockSupport.parkNanos(Object blocker, long deadline) 方法;
  • 调用带超时参数的 LockSupport.parkUntil(long deadline) 方法。
NEW与RUNNABLE状态的转换

刚创建的Thread对象是NEW状态,调用线程对象的start()方法就可以转换为RUNNABLE状态。

从RUNNABLE到TERMINATED状态

线程执行完 run() 方法后,会自动转换到 TERMINATED 状态,当然如果执行 run() 方法的时候异常抛出,也会导致线程终止。有时候我们需要强制中断 run() 方法的执行,可以调用 interrupt() 方法。

了解线程生命周期,在排查多线程bug的时候就可以更快更稳,加油打工人!