public class DaYaoGuai {static String s;public static void main(String[] args) {Thread t1 = new Thread(){@Overridepublic void run() {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}s="深圳";}};t1.start();Thread t2 = new Thread(){@Overridepublic void run() {while (s==null){System.out.println("空的");//屏蔽掉这一句,就永远都不会打印出"深圳";有这一句就能打印出"深圳"}System.out.println(s);}};t2.start();}
}
上面的这句 System.out.println("空的") 到底影响了什么?怎么会对结果有那么大影响?
2025年4月21日补充
先说结论:与内存模型、指令优化有关,没有System.out.println("空的")的话,该线程使用的将会一直是它工作内存里缓存的s,而System.out.println("空的")能触发同步进而刷新s。
这一代码存在线程安全方面的问题,因为静态变量s
未被volatile
关键字修饰。在 Java 里,每个线程都有自己的工作内存,线程操作变量时,先把变量从主内存复制到工作内存,操作完成之后再写回主内存。线程t1
修改了s
的值,不过这个修改在主内存里,线程t2
工作内存中的s
副本可能仍然是null
。要是线程t2
一直使用工作内存中的s
副本,那么while (s == null)
这个循环条件始终为真,循环不会结束,也就无法打印出 “深圳”。s
被volatile
关键字修饰后,线程t1
对s
的修改会马上刷新到主内存,线程t2
也会立即从主内存读取s
的最新值,这样就能保证线程t2
能正确打印出 “深圳”。
那么,为什么t1修改了s的值后,t2工作内存中的副本不能及时更新?
现代处理器为了提高性能,会采用缓存、指令重排序等优化手段。
- 缓存机制:处理器会将经常访问的数据存储在高速缓存中,以减少对主内存的访问次数。每个处理器核心都有自己的缓存,当一个线程在某个处理器核心上运行时,它对变量的操作是在该核心的缓存中进行的。当
t1
线程修改了s
的值,这个修改可能只存在于t1
所在处理器核心的缓存中,而t2
所在处理器核心的缓存中的s
值仍然是旧的。 - 指令重排序:为了提高程序的执行效率,编译器和处理器可能会对指令进行重排序。在
t2
线程中,编译器或处理器可能会对while (s == null)
这个循环进行优化,将s
的读取操作缓存起来,不再每次都从主内存中读取,从而导致t2
无法及时获取到s
的最新值。
在t2的while循环中加一句System.out.println("空的"),最终也能打印出“深圳",这又是什么原理?
1. 涉及同步
System.out.println
方法是一个同步方法,它的实现内部使用了 synchronized
块。在 Java 中,System.out
是 PrintStream
类的一个实例,println
方法的调用会进入一个同步代码块:
// PrintStream 类中的 println 方法
public void println(String x) {synchronized (this) {print(x);newLine();}
}
根据 Java 内存模型(JMM),进入同步代码块时,线程会从主内存中读取共享变量的最新值,退出同步代码块时,会将工作内存中的数据刷新到主内存。因此,在每次执行System.out.println("空的") 时,线程 t2
都会从主内存中读取 s
的最新值。也就是,synchronized会让线程清空自己的缓存,然后重新去主内存拷贝一份副本,这样,每次System.out.println()执行时其实就是刷新线程变量了。
2. 线程调度和上下文切换
在执行 System.out.println("空的") 时,由于这个操作涉及到 I/O 操作和同步机制,会导致线程 t2
发生上下文切换。上下文切换是指操作系统暂停当前正在执行的线程,保存其状态,然后选择另一个线程执行的过程。
在上下文切换过程中,线程 t2
会释放 CPU 资源,之后再次获得 CPU 资源继续执行时,会从主内存中重新加载共享变量的值。这样,线程 t2
就有机会获取到线程 t1
修改后的 s
的值,从而使 while (s == null)
条件不再成立,退出循环并打印出 “深圳”。
最后一个问题:如此,指令优化、缓存机制等这些为了提高效率的手段岂不是以带来新风险为代价的?
很不幸,确实是这样!坑也挺多的呀!