您好,登錄后才能下訂單哦!
這篇文章主要講解了“Java指令重排序在多線程環境下如何處理”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Java指令重排序在多線程環境下如何處理”吧!
指令重排在單線程環境下有利于提高程序的執行效率,不會對程序產生負面影響;在多線程環境下,指令重排會給程序帶來意想不到的錯誤。
下面給出一個能夠百分之百復原指令重排的例子。
ublic 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創建一個對象,大致分為一下過程:
在棧空間創建引用地址
以類文件為模版在堆空間對象分配內存
成員變量初始化
使用構造函數初始化
將引用值賦值給左側存儲變量
針對上述示例,假設第一個線程進入synchronized代碼塊,并開始創建對象,由于重排序存在,正常的創建對象過程被打亂,可能會出現在棧空間創建引用地址后,將引用值賦值給左側存儲變量,隨后因CPU調度時間片耗盡而產生中斷的情況。
后續線程在檢測到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)); } } }
當一組相關聯的變量發生指令重排時,使用原子操作類是比較優的解法。
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;
指令重排不僅限于Java程序,實際上各種編譯器均有指令重排的操作,從軟件到CPU硬件都有。指令重排是對單線程執行的程序的一種性能優化,需要明確的是,指令重排在單線程環境下,不會改變順序程序執行的預期結果。
上面討論了兩種典型多線程環境下指令重排,分析其帶來負面影響,并分別提供了應對方式。
對于關聯變量,先封裝成一個對象,然后使用原子類來操作
對于new對象,使用volatile關鍵字修飾目標對象即可
synchronized鎖通過互斥鎖,有序的保證線程訪問特定的代碼塊。代碼塊內部的代碼正常按照編譯器執行的策略重排序。
盡管synchronized鎖能夠回避多線程環境下重排序帶來的不利影響,但是互斥鎖帶來的線程開銷相對較大,不推薦使用。
synchronized 塊里的非原子操作依舊可能發生指令重排
感謝各位的閱讀,以上就是“Java指令重排序在多線程環境下如何處理”的內容了,經過本文的學習后,相信大家對Java指令重排序在多線程環境下如何處理這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。