先说结论

Java 线程有 NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING 和 TERMINATED 六种状态。线程协作可以使用监视器的 wait/notify、Lock 的 Condition、join、中断标记以及并发工具类;生产代码通常优先高层并发组件。

Java 的 RUNNABLE 同时包含操作系统层面的就绪和运行。BLOCKED 表示等待进入 synchronized,WAITING 则可能在等待通知、join 或同步器信号。

关键机制

wait 必须在持有对应监视器时调用,并会释放该锁;sleep 不要求持锁,也不会主动释放已持有的锁。notify 只唤醒一个等待者,notifyAll 唤醒全部等待者重新竞争。

六种状态如何区分

状态 含义 常见触发
NEW 已创建,尚未启动 new Thread(...)
RUNNABLE 可以被 JVM 调度运行 执行计算、等待 OS 调度、部分本地 I/O
BLOCKED 等待进入监视器锁 竞争 synchronized
WAITING 无限期等待其他线程动作 Object.wait()Thread.join()LockSupport.park()
TIMED_WAITING 有时限等待 sleep、带超时 wait/join/park
TERMINATED run 方法结束 正常返回或未捕获异常退出

JVM 的状态不是操作系统线程状态的逐字映射。例如 Java 的 RUNNABLE 可能实际正在运行,也可能已就绪等待 CPU;排查时要结合线程栈、CPU、锁拥有者和 I/O 指标,而不能仅凭状态下结论。

正确的条件等待代码

class BoundedBuffer<T> {
    private final Queue<T> queue = new ArrayDeque<>();
    private final int capacity;

    synchronized void put(T item) throws InterruptedException {
        while (queue.size() == capacity) {
            wait();
        }
        queue.add(item);
        notifyAll();
    }

    synchronized T take() throws InterruptedException {
        while (queue.isEmpty()) {
            wait();
        }
        T item = queue.remove();
        notifyAll();
        return item;
    }
}

必须使用 while 而不是 if:线程可能虚假唤醒,也可能在被 notifyAll 唤醒后,条件已被其他竞争者再次改变。notifyAll 虽会造成额外竞争,但在存在多个不同等待条件时更不容易发生“唤醒了错误线程后所有人继续等待”的死锁。

wait、sleep、join、park 对比

API 是否释放已持有监视器 需要什么前提 主要用途
wait 持有对应对象监视器 等待业务条件变化
sleep 暂停当前线程一段时间
join 调用方等待目标线程结束 目标线程对象 等待线程生命周期结束
park 不强制关联监视器 同步器底层阻塞与许可机制

不要用 Thread.sleep 轮询另一个线程是否完成,既浪费延迟又难以及时取消;可用 Future、CountDownLatch、CompletableFuture 或条件变量表达明确依赖。

中断的正确处理

中断不是强制杀线程,而是一面协作标记。阻塞在 wait、sleep、join 等可中断方法上的线程会收到 InterruptedException,并清除中断标记;捕获后应继续抛出,或在无法抛出的接口中恢复:

catch (InterruptedException e) {
    Thread.currentThread().interrupt();
    return;
}

吞掉中断会让线程池关闭、超时取消和应用停机无法及时生效。CPU 密集循环也要主动检查 isInterrupted() 并尽快退出。

线上排查路径

线程池请求堆积时,先看活跃线程是否 BLOCKED 在同一锁、WAITING 在队列、TIMED_WAITING 在不合理 sleep,还是 RUNNABLE 但 CPU 已满。连续抓取多份线程转储比单份更有价值:同一栈帧长时间不变才说明真正卡住。

正确协作

等待条件要写成 while (!condition) wait(),被唤醒后重新检查条件。中断是协作式取消信号,捕获 InterruptedException 后应向上抛出或恢复中断状态,不能静默吞掉。

容易踩坑的地方

调用 start() 才会启动新线程,直接调用 run() 只是当前线程的普通方法调用。notify 也不会立即释放锁,只有同步块退出后等待线程才可能继续。

常见问题

追问:为什么更推荐 BlockingQueue?

它把条件等待、唤醒、容量和可见性封装在明确 API 中,比手写 wait/notify 更不容易遗漏边界,并能自然表达生产消费与背压。

追问:调用 interrupt 后线程一定立即停止吗?

不一定。正在计算的线程只会设置中断标记,代码需要自行检查;可中断阻塞方法通常抛异常。无法响应中断的第三方调用还需要超时、隔离和进程级恢复策略。

追问:notify 能保证唤醒指定线程吗?

不能。它从该对象等待集里任选一个线程,调用方无法指定;有多个条件时应使用不同 Condition,或使用 notifyAll 后由每个线程重新判断条件。

追问:线程状态一直是 RUNNABLE 但接口很慢意味着什么?

可能是 CPU 密集循环、JNI/系统调用、忙等或线程在运行队列中等待 CPU。需结合 CPU 火焰图、系统负载和栈帧判断。