Java指令重排序在多線程環(huán)境下的處理方法
一、序言
指令重排在單線程環(huán)境下有利于提高程序的執(zhí)行效率,不會對程序產(chǎn)生負面影響;在多線程環(huán)境下,指令重排會給程序帶來意想不到的錯誤。
本文對多線程指令重排問題進行復(fù)原,并針對指令重排給出相應(yīng)的解決方案。
二、問題復(fù)原
(一)關(guān)聯(lián)變量
下面給出一個能夠百分之百復(fù)原指令重排的例子。
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; } }
1、結(jié)果預(yù)測
reader
方法僅在flag
變量為true時向控制臺打印變量a
的值。
writer
方法先執(zhí)行變量a
的賦值操作,后執(zhí)行變量flag
的賦值操作。
如果按照上述分析邏輯,那么控制臺打印的結(jié)果一定全為1。
2、指令重排
假如代碼未發(fā)生指令重排,那么當flag
變量為true時,變量a
一定為1。
上述代碼中關(guān)于變量a
和變量flag
在兩個方法類均存在指令重排的情況。
public static void writer() { a = 1; flag = true; }
通過觀察日志輸出,發(fā)現(xiàn)有大量的0輸出。
當writer
方法內(nèi)部發(fā)生指令重排時,flag
變量先完成賦值,此時假如當前線程發(fā)生中斷,其它線程在調(diào)用reader
方法,檢測到flag
變量為true,那么便打印變量a
的值。此時控制臺存在超出期望值的結(jié)果。
(二)new創(chuàng)建對象
使用關(guān)鍵字new創(chuàng)建對象時,因其非原子操作,故存在指令重排,指令重排在多線程環(huán)境下會帶來負面影響。
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;
1、解析創(chuàng)建過程
- 使用關(guān)鍵字new創(chuàng)建一個對象,大致分為一下過程:
- 在??臻g創(chuàng)建引用地址
- 以類文件為模版在堆空間對象分配內(nèi)存
- 成員變量初始化
- 使用構(gòu)造函數(shù)初始化
- 將引用值賦值給左側(cè)存儲變量
2、重排序過程分析
針對上述示例,假設(shè)第一個線程進入synchronized代碼塊,并開始創(chuàng)建對象,由于重排序存在,正常的創(chuàng)建對象過程被打亂,可能會出現(xiàn)在??臻g創(chuàng)建引用地址后,將引用值賦值給左側(cè)存儲變量,隨后因CPU調(diào)度時間片耗盡而產(chǎn)生中斷的情況。
后續(xù)線程在檢測到instance
變量不為空,則直接使用。因為單例對象并為實例化完成,直接使用會帶來意想不到的結(jié)果。
三、應(yīng)對指令重排
(一)AtomicReference原子類
使用原子類將一組相關(guān)聯(lián)的變量封裝成一個對象,利用原子操作的特性,有效回避指令重排問題。
@Data @NoArgsConstructor @AllArgsConstructor public class ValueModel { private Integer value; private Boolean flag; }
原子類應(yīng)該是解決多線程環(huán)境下指令重排的首選方案,不僅通俗易懂,而且線程間使用的非重量級互斥鎖,效率相對較高。
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)); } } }
當一組相關(guān)聯(lián)的變量發(fā)生指令重排時,使用原子操作類是比較優(yōu)的解法。
(二)volatile關(guān)鍵字
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;
四、指令重排的理解
1、指令重排廣泛存在
指令重排不僅限于Java程序,實際上各種編譯器均有指令重排的操作,從軟件到CPU硬件都有。指令重排是對單線程執(zhí)行的程序的一種性能優(yōu)化,需要明確的是,指令重排在單線程環(huán)境下,不會改變順序程序執(zhí)行的預(yù)期結(jié)果。
2、多線程環(huán)境指令重排
上面討論了兩種典型多線程環(huán)境下指令重排,分析其帶來負面影響,并分別提供了應(yīng)對方式。
- 對于關(guān)聯(lián)變量,先封裝成一個對象,然后使用原子類來操作
- 對于new對象,使用volatile關(guān)鍵字修飾目標對象即可
3、synchronized鎖與重排序無關(guān)
synchronized鎖通過互斥鎖,有序的保證線程訪問特定的代碼塊。代碼塊內(nèi)部的代碼正常按照編譯器執(zhí)行的策略重排序。
盡管synchronized鎖能夠回避多線程環(huán)境下重排序帶來的不利影響,但是互斥鎖帶來的線程開銷相對較大,不推薦使用。
synchronized 塊里的非原子操作依舊可能發(fā)生指令重排
到此這篇關(guān)于Java指令重排序在多線程環(huán)境下的應(yīng)對策略的文章就介紹到這了,更多相關(guān)Java指令重排序內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
maven導入本地倉庫jar包,報:Could?not?find?artifact的解決
這篇文章主要介紹了maven導入本地倉庫jar包,報:Could?not?find?artifact的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-03-03Spring動態(tài)管理定時任務(wù)之ThreadPoolTaskScheduler解讀
這篇文章主要介紹了Spring動態(tài)管理定時任務(wù)之ThreadPoolTaskScheduler解讀,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-12-12SpringCloud中Zuul網(wǎng)關(guān)原理及其配置
Spring?Cloud是一個基于Spring?Boot實現(xiàn)的微服務(wù)應(yīng)用開發(fā)工具,其中的Zuul網(wǎng)關(guān)可以實現(xiàn)負載均衡、路由轉(zhuǎn)發(fā)、鑒權(quán)、限流等功能,本文將從Spring?Cloud中Zuul網(wǎng)關(guān)的原理、使用場景和配置過程詳細介紹,幫助大家更好地了解和應(yīng)用Zuul網(wǎng)關(guān),需要的朋友可以參考下2023-06-06Java中BigDecimal比較大小的3種方法(??compareTo()、??equals()??和??compar
這篇文章主要給大家介紹了關(guān)于Java中BigDecimal比較大小的3種方法,方法分別是??compareTo()、??equals()??和??compareTo()??,在Java中使用BigDecimal類來進行精確的數(shù)值計算,需要的朋友可以參考下2023-11-11Java 可視化垃圾回收_動力節(jié)點Java學院整理
Ben Evans是一名資深培訓師兼顧問,他在演講可視化垃圾回收中從基礎(chǔ)談起討論了垃圾回收。以下是對其演講的簡短總結(jié)。感興趣的朋友一起學習吧2017-05-05springboot控制層傳遞參數(shù)為非必填值的操作
這篇文章主要介紹了springboot控制層傳遞參數(shù)為非必填值的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10