指令重排在單執行緒環境下有利於提高程式的執行效率,不會對程式產生負面影響;在多執行緒環境下,指令重排會為程式帶來意想不到的錯誤。
下面給出一個能夠百分之百復原指令重排的例子。
public class D { static Integer a; static Boolean flag; public static void writer() { a = 1; flag = true; } public static void reader() { if (flag != null && flag) { System.out.println(a); a = 0; flag = false; } } }
reader
方法僅在flag
變數為true時向控制台列印變數a
的值。
writer
方法先執行變數a
的賦值運算,後來執行變數flag
的賦值運算。
如果依照上述分析邏輯,那麼控制台列印的結果一定全為1。
假如程式碼未發生指令重排,那麼當flag
變數為true時,變數a
一定為1。
上述程式碼中關於變數a
和變數flag
在兩個方法類別都存在指令重排的情況。
public static void writer() { a = 1; flag = true; }
透過觀察日誌輸出,發現有大量的0輸出。
當writer
方法內部發生指令重排時,flag
變數先完成賦值,此時假如當前執行緒發生中斷,其它執行緒在呼叫reader
方法,偵測到flag
變數為true,那麼便列印變數a
的值。此時控制台存在超出期望值的結果。
使用關鍵字new建立物件時,因其非原子操作,故存在指令重排,指令重排在多執行緒環境下會帶來負面影響。
public class Singleton { private static UserModel instance; public static UserModel getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new UserModel(2, "B"); } } } return instance; } } @Data @AllArgsConstructor class UserModel { private Integer userId; private String userName; }
使用關鍵字new建立一個對象,大致分為一下過程:
在堆疊空間建立參考位址
以類別檔案為模版在堆疊空間物件中分配記憶體
後續執行緒在偵測到
instance變數不為空,則直接使用。因為單例物件並為實例化完成,直接使用會帶來意想不到的結果。 三、應對指令重排
@Data @NoArgsConstructor @AllArgsConstructor public class ValueModel { private Integer value; private Boolean flag; }
原子類別應該是解決多執行緒環境下指令重排的首選方案,不僅簡單易懂,而且執行緒間使用的非重量級互斥鎖,效率相對較高。
public class E { private static final AtomicReference<ValueModel> ar = new AtomicReference<>(new ValueModel()); public static void writer() { ar.set(new ValueModel(1, true)); } public static void reader() { ValueModel valueModel = ar.get(); if (valueModel.getFlag() != null && valueModel.getFlag()) { System.out.println(valueModel.getValue()); ar.set(new ValueModel(0, false)); } } }當一組相關聯的變數發生指令重排時,使用原子操作類別是比較優的解法。
(二)volatile關鍵字
public class Singleton { private volatile static UserModel instance; public static UserModel getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new UserModel(2, "B"); } } } return instance; } } @Data @AllArgsConstructor class UserModel { private Integer userId; private String userName; }
2、多執行緒環境指令重排
儘管synchronized鎖定能夠迴避多執行緒環境下重排序帶來的不利影響,但是互斥鎖帶來的執行緒開銷相對較大,不建議使用。
synchronized 區塊裡的非原子操作依舊可能發生指令重排以上是Java指令重排在多執行緒環境下怎麼解決的詳細內容。更多資訊請關注PHP中文網其他相關文章!