3. 线程状态与调度
大家好,我是你们的老朋友。今天咱们聊聊线程的状态流转和调度机制。说实话,这块内容我刚开始学的时候也觉得挺枯燥的,不就是几个状态来回切换嘛。但后来在线上排查过几次死锁问题后,我才真正体会到——不理解线程状态,你连问题出在哪都找不到。
一、线程的六种状态
Java里线程有6种状态,定义在 Thread.State 这个枚举里。我习惯把它们分成三组来看:
- NEW:刚new出来,还没调用start()
- RUNNABLE:就绪或正在运行
- BLOCKED:等着拿锁
- WAITING:无限期等待
- TIMED_WAITING:有限期等待
- TERMINATED:执行完毕
你想想看,一个线程从创建到销毁,其实就像人的一生。NEW是出生,RUNNABLE是干活,TERMINATED就是寿终正寝。中间那些等待状态,说白了就是「卡住了」。
核心要点:只有RUNNABLE状态的线程才能被调度器选中执行。其他状态都是在「等」——等锁、等通知、等时间到。
二、状态流转图
我画了一张图,把状态之间的流转关系理清楚了。这张图我每次培训都会拿出来讲,因为它太重要了。
这张图我建议你保存下来。每次遇到线程卡住的问题,对着图找状态,基本能定位个七七八八。
三、线程优先级
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状态,那代码肯定有问题,得赶紧排查。
六、避坑指南
这些年踩过的坑,总结几条给你:
- 别用stop()来终止线程。它会让线程直接进入TERMINATED状态,但不会释放锁,容易造成数据不一致。
- wait()和sleep()的区别:wait()会释放锁,sleep()不会。我见过有人用sleep()来等条件,结果死锁了。
- 线程状态不是越少越好。WAITING和BLOCKED虽然都是「等」,但原因不同。WAITING是主动等,BLOCKED是被动等。排查问题时一定要区分清楚。
好了,线程状态和调度这块就聊到这儿。记住一句话:理解线程状态,是写好并发程序的第一步。下次遇到线程卡住的问题,先jstack看看状态,再对症下药。
课后思考:如果一个线程长时间处于RUNNABLE状态但就是不执行,可能是什么原因?提示:看看是不是CPU被其他线程占满了,或者线程优先级被操作系统忽略了。