Skip to content

状态转换全解

线程在六种状态之间是怎么跳来跳去的?

转换全景图

                          start()
      ┌────────────────────────────────────────────────────┐
      │                                                     │
      ▼                                                     │
   NEW ───▶ RUNNABLE ───────────────────────────────────▶ TERMINATED
                │                                     │
                │ run() 结束 / 抛异常                        │
                │                                             │
                ├────── synchronized 等待锁 ────▶ BLOCKED ──┤
                │       (锁被占用)                  (获取到锁) │
                │                                             │
                ├────── wait() / join() / park() ──▶ WAITING │
                │       (无限期)                    (notify/unpark)│
                │                                             │
                └────── sleep() / wait(ms) / parkNanos() ──▶ TIMED_WAITING
                        (限时)                      (超时/被唤醒)

每条转换路径详解

路径一:NEW → RUNNABLE

调用 start() 方法:

java
Thread thread = new Thread(() -> {});

System.out.println(thread.getState());  // NEW

thread.start();

System.out.println(thread.getState());  // RUNNABLE

只能走一次,多次调用 start() 会抛 IllegalThreadStateException

路径二:RUNNABLE → BLOCKED

等待获取 synchronized 锁:

java
Object lock = new Object();

Thread holder = new Thread(() -> {
    synchronized (lock) {
        try {
            Thread.sleep(5000);  // 持有锁 5 秒
        } catch (InterruptedException e) {}
    }
});

Thread waiter = new Thread(() -> {
    synchronized (lock) {  // 等着拿锁
        System.out.println("拿到锁了");
    }
});

holder.start();
Thread.sleep(100);  // 确保 holder 先拿锁
waiter.start();

System.out.println(waiter.getState());  // BLOCKED

触发条件:其他线程持有了锁,自己想进但进不去。

离开方式:锁被释放后,JVM 会自动唤醒其中一个等待的线程。

路径三:RUNNABLE → WAITING

主动调用特定方法,无限期等待:

java
// 三种方式进入 WAITING:

// 方式1:wait()
synchronized (lock) {
    lock.wait();  // 必须持有锁才能调用
}

// 方式2:join() 无参数
thread.join();  // 等 thread 结束

// 方式3:LockSupport.park()
LockSupport.park();  // 不需要持有锁

WAITING 和 BLOCKED 的关键区别

BLOCKED: 被动等锁,锁一释放就被唤醒(不用通知)
WAITING: 主动让出,必须有人 notify 才醒

路径四:RUNNABLE → TIMED_WAITING

限时等待:

java
// 五种方式进入 TIMED_WAITING:

Thread.sleep(5000);  // 方式1:sleep

synchronized (lock) {
    lock.wait(5000);  // 方式2:wait 带超时
}

thread.join(5000);  // 方式3:join 带超时

LockSupport.parkNanos(5_000_000_000L);  // 方式4:park 限秒

condition.await(5, TimeUnit.SECONDS);  // 方式5:Condition 限秒

TIMED_WAITING 和 WAITING 的区别

WAITING: 无限期等,必须通知才醒
TIMED_WAITING: 限时等,超时自动醒

唤醒路径

WAITING → BLOCKED → RUNNABLE

wait()notify() 唤醒后,并不会直接进入 RUNNABLE,而是先进入 BLOCKED(因为还需要获取锁):

java
Thread waiting = new Thread(() -> {
    synchronized (lock) {
        try {
            lock.wait();  // RUNNABLE → WAITING
            // 被 notify 后:WAITING → BLOCKED
            // 获取到锁后:BLOCKED → RUNNABLE
        } catch (InterruptedException e) {}
    }
});

waiting.start();
Thread.sleep(100);

synchronized (lock) {
    lock.notify();  // 唤醒 waiting
    // waiting 此时是 BLOCKED(等锁),不是 RUNNABLE
}
// waiting 获取到锁,进入 RUNNABLE

为什么 notify 后不是直接 RUNNABLE?

因为 wait() 是在 synchronized 块里调用的,wait 期间会释放锁。notify 只是告诉等待的线程「可以醒了」,但醒了之后还需要重新获取锁。

TIMED_WAITING → RUNNABLE

超时到期或被中断:

java
Thread thread = new Thread(() -> {
    try {
        Thread.sleep(3000);  // 等 3 秒
    } catch (InterruptedException e) {}
});

thread.start();
Thread.sleep(100);

System.out.println(thread.getState());  // TIMED_WAITING

// 3 秒后...
thread.join();
System.out.println(thread.getState());  // TERMINATED

完整示例:跟踪状态变化

java
public class StateTransitionDemo {

    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();

        // 线程经历多种状态转换
        Thread thread = new Thread(() -> {
            // 1. RUNNABLE
            System.out.println("1. " + Thread.currentThread().getState());

            // 2. TIMED_WAITING(sleep)
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {}

            // 3. WAITING(wait)
            synchronized (lock) {
                try {
                    System.out.println("3. 进入 wait");
                    lock.wait();  // 等待被唤醒
                    System.out.println("6. 被唤醒了");
                } catch (InterruptedException e) {}
            }

            // 4. 继续运行,最终 TERMINATED
            System.out.println("7. 任务完成");
        }, "StateDemo");

        thread.start();

        Thread.sleep(100);
        System.out.println("2. " + thread.getState());  // TIMED_WAITING

        Thread.sleep(1100);
        System.out.println("4. " + thread.getState());  // WAITING

        Thread.sleep(100);
        System.out.println("5. " + thread.getState());  // WAITING(还在等锁)

        synchronized (lock) {
            System.out.println("6. 持有锁,调用 notify");
            lock.notify();
        }

        thread.join();
        System.out.println("8. " + thread.getState());  // TERMINATED
    }
}

输出

1. RUNNABLE
2. TIMED_WAITING
3. 进入 wait
4. WAITING
5. WAITING
6. 持有锁,调用 notify
6. 被唤醒了
7. 任务完成
8. TERMINATED

状态转换速查表

当前状态操作目标状态离开方式
NEWstart()RUNNABLE-
RUNNABLErun() 结束TERMINATED-
RUNNABLE等锁BLOCKED锁释放
RUNNABLEwait()WAITINGnotify() / notifyAll()
RUNNABLEjoin()WAITING线程结束 / interrupt()
RUNNABLEsleep(ms)TIMED_WAITING超时 / interrupt()
RUNNABLEpark()WAITINGunpark() / interrupt()
BLOCKED获取到锁RUNNABLE-
WAITINGnotify()BLOCKED获取到锁
WAITINGunpark()RUNNABLE-
TIMED_WAITING超时RUNNABLE-
TIMED_WAITING被唤醒BLOCKED获取到锁

实战:用 jstack 分析状态

线上出问题?用 jstack 看线程状态:

bash
# 查看 Java 进程的线程状态
jstack <pid>

# 查找处于 BLOCKED 状态的线程
jstack <pid> | grep -A 10 "java.lang.Thread.State: BLOCKED"

# 查找处于 WAITING 状态的线程
jstack <pid> | grep -A 10 "java.lang.Thread.State: WAITING"

jstack 常见状态解读

# BLOCKED - 等锁
"pool-1-thread-2" prio=5 tid=0x... nid=0x... waiting for monitor entry
   java.lang.Thread.State: BLOCKED

# WAITING - 等通知
"pool-1-thread-1" prio=5 tid=0x... nid=0x... in Object.wait()
   java.lang.Thread.State: WAITING

# TIMED_WAITING - 等超时
"pool-1-thread-3" prio=5 tid=0x... nid=0x... waiting on condition
   java.lang.Thread.State: TIMED_WAITING

总结

  • 线程状态转换是单向的:NEW → RUNNABLE → TERMINATED
  • BLOCKED 是被动等锁,WAITING 是主动等待
  • TIMED_WAITING 是带超时的等待
  • wait()notify() 唤醒后,先进入 BLOCKED(需要重新获取锁)
  • 理解状态转换是分析死锁、性能问题的前提

基于 VitePress 构建