MESI場景
下面這個程序緩存一致性問題,initFlag第二個線程更改為true後,如果沒有volatile關鍵字修飾,thread1是無法可見數據已被修改,不會跳出while循環。
可見性實現一
當對代碼initFlag進行修飾關鍵字volatile時,可實現數據修改的可見性
可見性實現二
如上圖Console打印結果,很奇怪為什麼initFlag沒有被volatile修飾也可以實現initFlag變量的可見性,那是為什麼呢?為什麼線程1能夠將自己的工作內存副本清除去主內存獲取到線程2更改後的值呢?(不用volatile關鍵字)
答案是MESI緩存一致性協議的原理所致,當MESI收到一個#LOCK彙編指令時,會強制所有的線程的副本數據進行數據同步,那#LOCK彙編指令在Java中,是如何體現出來,自然是synchronized。
我們只是增加了一行,就實現了volatile的功能
\t\t\t\t\tSystem.out.println("進來了");
我們看下源碼就知道了
/** * Prints a String and then terminate the line. This method behaves as * though it invokes <code>{@link #print(String)}/<code> and then * <code>{@link #println()}/<code>. * * @param x The <code>String/<code> to be printed.
*/
public void println(String x) {
synchronized (this) {
print(x);
newLine();
}
}
源碼裡有synchronized同步代碼塊,在線程中存在同步塊時,會強制去主內存重新獲取主內存的值 可以試下把線程1的while循環裡的System.out.println()改為普通的賦值語句int a = 0;則線程1會卡住死循環。
小結
MESI的數據同步條件為收到一個#LOCK彙編指令時,就會進行線程中的副本數據同步,而上面的例子中,只是用了一個取巧的案例來說明其中的緣由。
閱讀更多 油膩的Java 的文章