1. 线程状态转换:从创建到销毁的全生命周期
在Java多线程编程中,理解线程状态转换是构建稳定并发程序的基础。每个Java线程在其生命周期中会经历6种明确的状态,这些状态在Thread.State枚举类中有明确定义:
java复制public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
1.1 NEW状态:线程的诞生
当使用new Thread()创建线程对象后,线程就处于NEW状态。此时线程尚未启动,可以理解为"纸上蓝图"阶段。这个状态下线程对象已经分配内存,但操作系统级别的线程资源还未创建。
注意:直接调用已终止线程的start()方法会抛出IllegalThreadStateException。这是新手常犯的错误。
1.2 RUNNABLE状态:准备就绪
调用start()方法后,线程进入RUNNABLE状态。这个状态包含两个子状态:
- 就绪(Ready):线程等待CPU时间片分配
- 运行中(Running):线程正在CPU上执行
可以用电影院排队看热门电影来类比:
- 买票成功(NEW)
- 排队等待检票(Ready)
- 正在观影(Running)
java复制Thread thread = new Thread(() -> {
System.out.println("线程执行中");
});
thread.start(); // 状态变为RUNNABLE
1.3 阻塞与等待状态
当线程需要等待外部条件时,会进入以下三种状态之一:
1.3.1 BLOCKED状态
线程等待获取监视器锁(monitor lock)时的状态。典型场景:
- 进入synchronized方法/代码块时发现锁已被其他线程持有
- 调用Object.wait()后重新获取锁时
java复制synchronized(lock) { // 如果锁被占用,线程将BLOCKED
// 临界区代码
}
1.3.2 WAITING状态
线程无限期等待其他线程执行特定操作。触发方式:
- Object.wait()(需配合notify()/notifyAll())
- Thread.join()(等待目标线程终止)
- LockSupport.park()
1.3.3 TIMED_WAITING状态
与WAITING类似,但带有时间限制。常见方法:
- Thread.sleep(long)
- Object.wait(long)
- Thread.join(long)
- LockSupport.parkNanos()
1.4 TERMINATED状态:线程终结
线程执行完run()方法或抛出未捕获异常后进入TERMINATED状态。此时:
- 线程栈和程序计数器等资源已被释放
- 不能再通过start()重启
- 仍可访问线程对象的其他属性和方法
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 状态转换的触发条件与典型场景
2.1 从NEW到RUNNABLE的启动过程
调用start()方法时,JVM会执行以下操作:
- 检查线程状态,确保是NEW状态
- 将线程加入线程调度队列
- 调用native方法在操作系统层面创建线程
- 线程状态变更为RUNNABLE
重要:直接调用run()方法不会启动新线程,只是在当前线程同步执行方法内容。
2.2 RUNNABLE到WAITING的转换
典型场景示例:
java复制// 场景1:wait()调用
synchronized(sharedObject) {
sharedObject.wait(); // 释放锁并进入WAITING
}
// 场景2:join()调用
Thread worker = new WorkerThread();
worker.start();
worker.join(); // 当前线程WAITING直到worker结束
2.3 从WAITING到RUNNABLE的恢复
对应上述场景的恢复方式:
java复制// 对应wait()
synchronized(sharedObject) {
sharedObject.notify(); // 唤醒单个线程
// sharedObject.notifyAll(); // 唤醒所有等待线程
}
// join()自动恢复无需干预
2.4 BLOCKED状态的产生与解除
BLOCKED状态通常出现在锁竞争场景:
java复制public class Counter {
private int count = 0;
public synchronized void increment() {
count++; // 如果锁被占用,调用线程将BLOCKED
}
}
解除BLOCKED的唯一方式是持有锁的线程释放锁(退出同步块或调用wait())。
3. Java内存模型(JMM)基础
3.1 为什么需要内存模型
在多核CPU架构下,存在以下问题:
- 缓存一致性问题:各CPU核心有独立缓存
- 指令重排序问题:编译器和处理器会优化指令顺序
JMM通过规范线程如何与内存交互,解决可见性和有序性问题。
3.2 主内存与工作内存
JMM的核心概念:
- 主内存(Main Memory):所有共享变量的存储位置
- 工作内存(Working Memory):每个线程私有的内存空间
线程对变量的所有操作都必须在工作内存中进行,不能直接读写主内存。
3.3 happens-before原则
JMM定义的一系列保证内存可见性的规则,包括:
- 程序顺序规则:线程内操作按代码顺序执行
- 锁规则:解锁操作happens-before后续的加锁操作
- volatile规则:volatile写happens-before后续读
- 线程启动规则:线程A启动线程B,那么A的操作对B可见
- 线程终止规则:线程B终止前所有操作对检测到B终止的线程可见
4. JMM在并发编程中的实际应用
4.1 volatile关键字
volatile解决的是可见性问题:
- 保证变量的修改立即刷新到主内存
- 禁止指令重排序优化
适用场景:
- 状态标志位
- 单例模式的双重检查锁定
java复制public class Singleton {
private static volatile Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
4.2 synchronized的内存语义
synchronized不仅提供互斥,还保证:
- 进入同步块前,清空工作内存,从主内存重新加载变量
- 退出同步块时,将工作内存中的变量刷新到主内存
4.3 final字段的特殊规则
正确构造的final字段具有特殊的内存语义:
- 构造函数中对final字段的写入,与随后将被构造对象的引用赋值给引用变量,这两个操作不能重排序
- 初次读包含final字段的对象引用,与随后初次读这个final字段,这两个操作不能重排序
5. 线程状态与内存模型的交互影响
5.1 状态转换对内存可见性的影响
线程状态转换会触发内存屏障:
- 从BLOCKED转为RUNNABLE(获取锁时):相当于进入synchronized块
- 从WAITING/TIMED_WAITING唤醒:可能重新加载变量值
5.2 常见内存可见性问题案例
案例1:不可见性问题
java复制public class VisibilityProblem {
private static boolean ready = false;
private static int number = 0;
public static void main(String[] args) {
new Thread(() -> {
while (!ready) {
// 可能永远循环
}
System.out.println(number);
}).start();
number = 42;
ready = true;
}
}
解决方案:将ready声明为volatile
案例2:指令重排序问题
java复制public class ReorderingExample {
private int x = 0;
private int y = 0;
public void writer() {
x = 1;
y = 2;
}
public void reader() {
if (y == 2) {
System.out.println(x); // 可能输出0
}
}
}
解决方案:使用volatile或synchronized
6. 实战中的注意事项与性能考量
6.1 线程状态监控最佳实践
- 使用jstack工具分析线程状态:
bash复制jstack <pid> > thread_dump.txt
- 识别常见问题模式:
- 大量线程BLOCKED:锁竞争激烈
- 线程长时间WAITING:可能死锁或资源不足
- RUNNABLE线程过多:可能线程池配置不合理
6.2 锁优化技巧
- 减小锁粒度:拆分大锁为多个小锁
- 锁分离:读写锁分离(ReentrantReadWriteLock)
- 无锁编程:使用Atomic类(CAS操作)
- 偏向锁/轻量级锁:JVM自动优化
6.3 内存屏障的使用
显式插入内存屏障的方法:
java复制// 使用Unsafe类(不推荐)
Unsafe.getUnsafe().storeFence();
// 使用VarHandle(Java9+)
VarHandle.fullFence();
7. Java并发工具类与JMM
7.1 ConcurrentHashMap的实现原理
利用分段锁和volatile变量保证线程安全:
- 表数组用volatile修饰保证可见性
- 使用CAS操作实现无锁化的节点插入
7.2 Atomic类的底层机制
基于CAS(Compare-And-Swap)和volatile:
java复制public class AtomicInteger {
private volatile int value;
public final int incrementAndGet() {
return U.getAndAddInt(this, VALUE, 1) + 1;
}
}
7.3 CountDownLatch的内存语义
内部使用AbstractQueuedSynchronizer实现:
- countDown()操作具有volatile写的内存效应
- await()操作具有volatile读的内存效应
8. Java虚拟线程与JMM
Java 19引入的虚拟线程(协程)也遵循JMM规范:
- 虚拟线程挂起时相当于进入WAITING状态
- 恢复执行时保证内存可见性
- 与平台线程共享相同的内存模型约束
java复制try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> {
System.out.println("Virtual thread running");
});
}
