Java线程生命周期

1. 操作系统线程生命周期

操作系统线程生命状态有5种。

  • 初始状态(New)
    进程正在被创建时的状态。仅为线程对象开辟了一块堆内存,实际上线程在操作系统层面还未创建。

  • 就绪状态 (Ready)
    可运行,由于其他进程处于运行状态而暂时停止运行

  • 运行状态 (Running)
    该进程此时正占用CPU

  • 阻塞状态 (Blocked)
    该进程正在等待某一事件发生(如等待IO操作)而暂时停止运行,此时给它CPU控制权也无法运行

  • 终止状态 (Terminated)
    进程正在从系统中消失的状态

2. JVM线程生命周期

JVM线程状态有6种。

  • 初始状态(New)

  • 就绪/运行状态(Runnable)

  • 阻塞状态(Blocked)

  • 无时限等待(Waiting)

  • 有时限等待(Timed_Waiting)

  • 终止状态(Terminated)

其中将操作系统的就绪及运行状态合并为运行状态,将阻塞状态又细分为了阻塞状态、无时限等待、有时限等待。
image

2.1 线程状态切换

JVM中的线程状态切换与操作系统中有所不同。

1. New -> Runnable

创建出来的线程对象为New状态,调用start()方法便可以转为RUnnbale状态。

2. Runnable -> Blocked

JVM中,只有线程等待synchronized隐式锁,才会从Runnable转为Blocked。对于等待IO的行为,JVM依然认为是Runnable。

3. Runnable -> Waiting

  • 拥有synchronized隐式锁的线程调用wait()方法

  • 调用Thread实例的join()方法

  • 调用LockSupport.park()方法
    LockSupport.park()是Java中的一个方法,它用于暂停当前线程的执行,直到它被unpark,或者线程被中断。
    LockSupport是Java并发包(java.util.concurrent)中的一个类,它提供了一些工具来帮助实现并发控制。Park方法是一个实例方法,它允许一个线程主动让出CPU的执行时间片,暂停当前线程的执行。在调用park方法后,当前线程会变为非活动状态,即暂时不会消耗CPU的时间片,直到满足某种条件(如另一个线程调用unpark方法或者线程被中断等)才会重新变为活动状态。
    需要注意的是,LockSupport.park()并不会释放任何锁,也就是说,即使调用了park()方法,当前线程持有的锁也不会被自动释放。因此,在使用LockSupport.park()时,需要谨慎处理并发控制,以避免出现死锁等问题。
    在实践中,LockSupport.park()常用于实现忙等待(busy-waiting)的场景,例如在生产者-消费者问题中,当缓冲区已满时,生产者线程可以调用LockSupport.park()让出CPU的控制权,等待消费者线程消费数据后调用LockSupport.unpark()唤醒它。
    虽然LockSupport.park()让出CPU的控制权,但是没有释放任何资源,所以依然被叫作忙等待。

4. Runnable -> Timed_Waiting

除了新增的Thead.sleep()方法外,其余都是Waiting方法中对应新增了超时参数的方法。

  • 拥有synchronized隐式锁的线程调用wait(long timeout)方法

  • 调用Thread实例的join(long millis)方法

  • 调用带超时参数的 LockSupport.parkNanos(Object blocker, long deadline) 方法

  • 调用带超时参数的 LockSupport.parkUntil(long deadline) 方法。

  • 调用带超时参数的Thread.sleep(long millis) 方法;

5. Runnable -> Terminated

  • 执行完自动进入Terminated状态

  • 中断执行
    使用interrupt()方法,代码中需要实现对中断状态的捕获。Waiting/Timed_Waiting状态代码都实现了捕获,会转为Runnable状态并抛出InterruptedException异常。

3. 利用线程栈信息定位

理解 Java 线程的各种状态以及生命周期对于诊断多线程 Bug 非常有帮助,多线程程序很难调试,出了 Bug 基本上都是靠日志,靠线程 dump 来跟踪问题,分析线程 dump 的一个基本功就是分析线程状态,大部分的死锁、饥饿、活锁问题都需要跟踪分析线程的状态。

可以通过 jstack 命令或者Java VisualVM这个可视化工具将 JVM 所有的线程栈信息导出来,完整的线程栈信息不仅包括线程的当前状态、调用栈,还包括了锁的信息。如下图所示。

posted @ 2023-11-24 15:27  kiper  阅读(3)  评论(0编辑  收藏  举报