线程状态与调度:从生到死的完整旅程

大家好,我是你们的老朋友。今天咱们聊聊线程的状态流转和调度机制。说实话,这块内容我刚开始学的时候也觉得挺枯燥的,不就是几个状态来回切换嘛。但后来在线上排查过几次死锁问题后,我才真正体会到——不理解线程状态,你连问题出在哪都找不到。

一、线程的六种状态

Java里线程有6种状态,定义在 Thread.State 这个枚举里。我习惯把它们分成三组来看:

  • NEW:刚new出来,还没调用start()
  • RUNNABLE:就绪或正在运行
  • BLOCKED:等着拿锁
  • WAITING:无限期等待
  • TIMED_WAITING:有限期等待
  • TERMINATED:执行完毕

你想想看,一个线程从创建到销毁,其实就像人的一生。NEW是出生,RUNNABLE是干活,TERMINATED就是寿终正寝。中间那些等待状态,说白了就是「卡住了」。

核心要点:只有RUNNABLE状态的线程才能被调度器选中执行。其他状态都是在「等」——等锁、等通知、等时间到。

二、状态流转图

我画了一张图,把状态之间的流转关系理清楚了。这张图我每次培训都会拿出来讲,因为它太重要了。

线程六种状态流转图 NEW RUNNABLE BLOCKED WAITING TIMED_WAITING TERMINATED start() 执行完毕 拿不到锁 拿到锁 wait() / join() notify() / notifyAll() sleep(time) / wait(time) 超时 / 被唤醒 图例: 主动转换 竞争失败 等待 结束

这张图我建议你保存下来。每次遇到线程卡住的问题,对着图找状态,基本能定位个七七八八。

三、线程优先级

Java里优先级范围是1到10,默认是5。但说实话,这东西在实际开发中作用有限。为什么?

  • 不同操作系统对优先级的映射不一样。Windows有7级,Linux有?嗯,Linux其实直接忽略Java的优先级设置。
  • 优先级高不代表一定会先执行,它只是给调度器一个「建议」。

我曾经在做一个实时数据处理系统时,试图通过调高优先级来保证某个关键线程的响应速度。结果呢?在Windows上测试好好的,一部署到Linux服务器上,完全没效果。后来查资料才知道,Linux的线程调度器根本不认Java的优先级设置。

所以我的建议是:别依赖优先级来保证执行顺序。真要控制顺序,用锁、信号量或者队列这些同步机制更靠谱。

四、线程调度算法

调度算法这块,不同操作系统实现不同。但主流的有这么几种:

调度算法 特点 适用场景
时间片轮转 每个线程分一个时间片,用完就切换 通用操作系统,公平性好
优先级调度 优先级高的先执行,可抢占 实时系统,响应要求高
多级反馈队列 多个队列,不同优先级,动态调整 现代操作系统,兼顾公平和效率

Java本身不直接实现调度算法,它依赖底层操作系统的线程调度。说白了,Java线程就是操作系统线程的「马甲」。

五、实战中的状态诊断

我在线上排查问题时,最常用的工具就是 jstack。它能打印出所有线程的当前状态和堆栈信息。给你看个例子:

// 模拟一个死锁场景
public class DeadLockDemo {
    private static final Object lockA = new Object();
    private static final Object lockB = new Object();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (lockA) {
                System.out.println("线程1拿到锁A");
                try { Thread.sleep(100); } catch (Exception e) {}
                synchronized (lockB) {
                    System.out.println("线程1拿到锁B");
                }
            }
        });

        Thread t2 = new Thread(() -> {
            synchronized (lockB) {
                System.out.println("线程2拿到锁B");
                try { Thread.sleep(100); } catch (Exception e) {}
                synchronized (lockA) {
                    System.out.println("线程2拿到锁A");
                }
            }
        });

        t1.start();
        t2.start();
    }
}

跑起来后,用 jstack <pid> 查看,你会看到类似这样的输出:

"Thread-1" #12 prio=5 os_prio=0 tid=0x... nid=0x... waiting for monitor entry
    - waiting to lock <0x...> (a java.lang.Object)
    - locked <0x...> (a java.lang.Object)

"Thread-0" #11 prio=5 os_prio=0 tid=0x... nid=0x... waiting for monitor entry
    - waiting to lock <0x...> (a java.lang.Object)
    - locked <0x...> (a java.lang.Object)

看到没?两个线程都在BLOCKED状态,互相等着对方释放锁。这就是典型的死锁。

我个人习惯:每次上线前,我都会用jstack跑一遍关键线程的状态快照。如果发现有大量线程处于BLOCKED或WAITING状态,那代码肯定有问题,得赶紧排查。

六、避坑指南

这些年踩过的坑,总结几条给你:

  1. 别用stop()来终止线程。它会让线程直接进入TERMINATED状态,但不会释放锁,容易造成数据不一致。
  2. wait()和sleep()的区别:wait()会释放锁,sleep()不会。我见过有人用sleep()来等条件,结果死锁了。
  3. 线程状态不是越少越好。WAITING和BLOCKED虽然都是「等」,但原因不同。WAITING是主动等,BLOCKED是被动等。排查问题时一定要区分清楚。

好了,线程状态和调度这块就聊到这儿。记住一句话:理解线程状态,是写好并发程序的第一步。下次遇到线程卡住的问题,先jstack看看状态,再对症下药。

课后思考:如果一个线程长时间处于RUNNABLE状态但就是不执行,可能是什么原因?提示:看看是不是CPU被其他线程占满了,或者线程优先级被操作系统忽略了。

可可精品资料库