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)
?條件不再成立,退出循環并打印出 “深圳”。
最后一個問題:如此,指令優化、緩存機制等這些為了提高效率的手段豈不是以帶來新風險為代價的?
很不幸,確實是這樣!坑也挺多的呀!