一、指令重排序案例
指令重排序是CPU為了提高指令執行效率的一種優化方式,它會將指令按照一定規則重新排序。以下是一個指令重排序的經典案例:
public class DCLSingleton { private static volatile DCLSingleton instance; private DCLSingleton() {} public static DCLSingleton getInstance() { if (instance == null) { synchronized (DCLSingleton.class) { if (instance == null) { instance = new DCLSingleton(); } } } return instance; } }
在並發情況下,由於指令重排序的存在,可能會引發DCL(Double-Check Locking)失敗的問題,使得單例模式失效。
二、指令重排序和JMM的關係
Java內存模型(Java Memory Model,JMM)是Java平台中各種編譯器的內存模型的抽象。為了支持跨平台的Java編程,JMM規範了不同線程之間的內存可見性、數據原子性、指令重排序等問題,並提供了一套完整的內存屏障(Memory Barrier)語義規範。
在JMM的規範下,JVM對於指令重排序有以下保證
- 單線程中,按照指令出現的順序執行。
- 多線程中,無法保證按照指令出現的順序執行,但會保證在JMM規定下的「內存屏障」出現的時候,程序執行滿足JMM的需求。
三、指令重排序什麼意思
指令重排序是CPU為了提高指令執行效率而進行的一種優化方式。由於現代CPU都採用了流水線技術,當一條指令執行完後,下一條指令隨即進入流水線被執行。而在執行過程中,如果出現了數據相關等問題,就需要停頓流水線等待數據,這樣會降低CPU效率。為了儘可能避免出現這種情況,CPU引入了指令重排序技術,將原來的指令序列重新排序,儘可能保證流水線能夠無延遲地執行指令。
四、指令重排序會有什麼問題
雖然指令重排序能夠提高系統的效率,但也可能引發一些隱患。最典型的例子是DCL。
在DCL的代碼片段中,變量instance被聲明為volatile,該關鍵字表示變量的修改對於其他線程都是可見的。但是如果在synchronized關鍵字後面的重排序過程中,創建對象的構造函數先執行,而在給instance賦值的過程中出現了重排序,則會引發DCL的失敗。
五、指令重排單線程有問題
單線程在執行指令重排時,也存在隱患。以下這個例子可以說明這個問題:
public class Test { public static void main(String[] args) { int a = 0; boolean flag = false; a = 1; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if (a == 1 && !flag) { System.out.println("a==1"); } } }
在單線程中執行以上代碼,有可能導致輸出結果為空。這是因為JVM為了提高執行效率,可能會將if語句執行的代碼片段與a=1這條語句進行重排序,導致程序邏輯錯誤。
六、代碼示例
public class Test { public static void main(String[] args) { int a = 0; boolean flag = false; a = 1; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if (a == 1 && !flag) { System.out.println("a==1"); } } }
以上代碼就體現了指令重排單線程的隱患。在多線程中,也要注意指令重排序可能引發的問題。
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-hk/n/191044.html