[Java] 线程的状态及转换

一、线程的状态简介

JDK 的线程状态枚举类 java.lang.Thread.State ,定义了线程的6种状态:

  • NEW: 新建线程,尚未调用 thread.start()
  • RUNNABLE: 可在 JVM 中执行,需等待CPU资源
  • BLOCKED: 调用了 Synchronized 方法或语句块,等待对象锁资源
  • WAITING: 调用了 Object#wait(), Thread#join(), LockSupport#park() 等方法中的一个,等待其他线程执行特定的动作 (notify 或 线程终止)
  • TIMED_WAITING: 调用了 Thread#sleep(long), Object#wait(long), Thread#join(long), LockSupport#parkNanos, LockSupport#parkUntil 等方法中的一个
  • TERMINATED: 线程执行完毕

二、线程的状态转换

线程状态转换

注:上面列出的为线程的正常状态流转,可调用 Thread#interrupt() 中断。

RUNNABLE

RUNNABLE 状态表示该线程已就绪,可运行,但不一定能立刻执行,还需等待CPU 调度 。

BLOCKED

  1. 线程进入 Synchronized 方法或语句块;
  2. 线程调用 Object#wait 方法被 Object#notify/notifyAll 唤醒后,变为 RUNNABLE 状态,并重新竞争 monitor 锁;

注:线程需要先竞争 monitor 锁成功,再等待 CPU 调度。

WAITING

线程调用 Object#wait(), Thread#join(), LockSupport#park() 等方法中的一个,进入 WAITING 状态。需要其他线程调用 Object#notity() 或 Object#notifyAll() 方法唤醒线程,否则一直等待。

TIMED_WAITING

线程调用 Thread#sleep(long), Object#wait(long), Thread#join(long), LockSupport#parkNanos, LockSupport#parkUntil 等方法中的一个,进入 TIMED_WAITING 状态。其他线程调用 Object#notity(), Object#notifyAll() 或超时时间到达,唤醒线程。

三、monitor 和 wait set

Java Language Specification 的描述中,Java 每个对象都有一个关联的 monitor(监视器) 和 wait set(等待队列),通过 Synchronizedwait/notify 进行转换:

  1. 线程通过 Synchronized 获取 monitor 锁,多线程时,未获取到锁的线程进入 Entry Set 等待;
  2. 已获取 monitor 锁的线程,通过调用 Object#wait 方法,进入 Wait Set 等待区,并释放 monitor 锁;
  3. 其他线程通过 Object#notify, Object#notifyAll 方式唤醒 Wait Set 区内的线程,竞争 对象 Monitor 锁。

图示如下:

monitor and wait set

注意1:在调用 Object#wait, Object#notify, Object#notifyAll 等方法之前,必须先获得该对象的 monitor

注意2: 通过调用 Synchronized 获取对象的 monitor 锁的方式有:

class Ex {
    private Object L = new Object();

    // 锁对象是Ex.class
    public static synchronized A() { ... }
    
    // 锁对象是当前对象(this)
    public synchronized A() { ... }
    
    public A() {
        // 锁对象是L
        synchronized(L) {
            ...
        }
    }
}

四、方法说明

Thread#sleep(long)

调用后进入 TIMED_WAITING 状态,使当前线程休眠一定时间,不会释放对象锁,时间到了,则线程进入 RUNNABLE 状态。

Object#wait()/Object#wait(long)

调用后线程状态变为 WAITING/TIMED_WAITING,进入等待队列 (wait set),并释放监视器锁

注:当前线程必须先获得监视器锁,否则抛 IllegalMonitorStateException 异常。

Thread#join()

等待当前线程消亡,同步方法。内部通过判断线程 isAlive() 状态,循环调用 Object#wait(long)

Object#notify()/notifyAll()

notify: 唤醒一个等待队列中的线程,如果有多个线程,则由 JVM 任意挑选一个。
notifyAll: 唤醒所有等待队列中的线程。

其他线程调用 Object#notify()/notifyAll() 后,当前线程变为 RUNNABLE,并重新竞争 monitor 锁,竞争成功状态变为 RUNNABLE,竞争失败变为 BLOCKED

调用 notify/notifyAll 后,调用线程并不一定立即释放 monitor 锁,需等待对应逻辑执行完,即线程为 terminated 或被中断,才会释放。

注:当前线程必须先获得监视器锁,否则抛 IllegalMonitorStateException 异常。

Thread#yield()

提示 CPU 可以释放当前线程占用的 CPU 资源,防止 CPU 过度使用,但 CPU 可以忽略这个提示。

让出 CPU 资源后,当前线程变为 RUNNABLE 状态,继续和其他线程竞争 CPU 资源。

Thread#interrupt()

可中断正在运行的线程。需要注意的点有:

  • 如果中断的不是正在运行的线程,会调用 java.lang.Thread#checkAccess 方法,可能抛出 java.lang.SecurityException 异常
  • 如果线程阻塞在 wait, join, sleep 等方法时,中断状态会被清除,并抛出 java.lang.InterruptedException 异常
  • 如果线程阻塞在使用 java.nio.channels.InterruptibleChannel 的 I/O 操作,会关闭管道,设置中断状态,抛出 java.nio.channels.ClosedByInterruptException 异常
  • 如果线程阻塞在 java.nio.channels.Selector设置中断状态,并立即返回,和调用 Selector#wakeup() 方法一样
  • 如果不是以上列出的情形,则设置中断状态
  • 不会对已终结的线程造成影响

五、如何查看线程状态

使用 jstack <pid>命令,可以查看进程的线程信息。

RUNNABLE

运行中:

runnable

等待资源:

waiting

WAITING

调用 Object.wait():

Object#wait()

调用 LockSupport#park():

LockSupport#park()

TIMED_WAITING

调用 Object#wait(long):

Object#wait(long)

调用 Thread#sleep(long):

Thread#sleep(long)

调用 LockSupport#parkNanos(long):

LockSupport#parkNanos

参考资料

java.lang.Thread.State
Java Language Specification
Java线程同步机制
Monitors – The Basic Idea of Java Synchronization
Java线程的6种状态及切换(透彻讲解)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值