
程序未死锁却无法退出,根本原因在于主线程无法及时看到t2线程中断状态及共享变量x的更新——这是典型的内存可见性问题,需通过volatile修饰或同步机制保证jvm指令重排序和缓存一致性。
程序未死锁却无法退出,根本原因在于主线程无法及时看到t2线程中断状态及共享变量x的更新——这是典型的内存可见性问题,需通过volatile修饰或同步机制保证jvm指令重排序和缓存一致性。
该问题表面看是线程控制逻辑异常,实则暴露了Java内存模型(JMM)中变量可见性与指令重排序的关键约束。原始代码中,static int x 和 t2.isInterrupted() 均未被声明为 volatile,导致:
- 主线程在 while(!t2.isInterrupted()) 循环中可能永久缓存t2.isInterrupted()的旧值(如false),即使t2已被中断;
- t2线程读取x时,也可能看不到t1写入的x = 10,因缺少happens-before关系保障。
为什么加System.out.println("t2 completed")就“修复”了?
该语句触发了隐式内存屏障:println内部调用synchronized方法(如PrintStream.write()),强制刷新本地缓存、同步主内存,使t2对interrupted状态的修改对主线程可见。但这属于偶然副作用,不可依赖。
正确解决方案(推荐)
✅ 方案1:使用volatile修饰关键变量
static volatile int x; // 保证x的写操作对所有线程立即可见 // 同时,Thread.isInterrupted()本身是volatile语义,但循环中仍需避免JIT优化
✅ 方案2:显式同步 + volatile(更健壮)
static volatile int x;
static volatile boolean t2Done = false;
public static void main(String[] args) {
Thread t2 = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
// 空循环易被JIT优化为无限循环(无内存访问)
}
System.out.println(x);
t2Done = true; // volatile写,建立happens-before
}, "t2");
t2.start();
new Thread(() -> {
sleep(1);
x = 10; // volatile写 → 对t2可见
t2.interrupt(); // 中断信号 + 内存屏障
}, "t1").start();
// 等待t2完成(volatile读确保可见性)
while (!t2Done) {
Thread.yield();
}
System.out.println(x);
}⚠️ 注意事项:
- 空while(true)循环无内存访问时,JIT编译器可能将其优化为无限循环(不重新读取isInterrupted()),这是JVM合法优化;
- Thread.yield()不提供内存同步语义,不能替代volatile或synchronized;
- sleep()、wait()、join()等阻塞方法会隐式刷新内存,但yield()不会;
- Thread.interrupted()会清除中断状态,应优先使用isInterrupted()保持状态可检测。
总结
本例本质是JMM实践陷阱:没有volatile/synchronized,就没有可靠的跨线程内存可见性。不要依赖System.out.println等副作用“修复”并发问题。务必对共享状态变量使用volatile,或采用Lock/synchronized构建明确的happens-before关系——这才是线程安全的基石。


















