中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Java指令重排在多線程環境下怎么解決

發布時間:2022-04-24 10:06:38 來源:億速云 閱讀:136 作者:iii 欄目:開發技術

本篇內容介紹了“Java指令重排在多線程環境下怎么解決”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

一、序言

指令重排在單線程環境下有利于提高程序的執行效率,不會對程序產生負面影響;在多線程環境下,指令重排會給程序帶來意想不到的錯誤。

二、問題復原

(一)關聯變量

下面給出一個能夠百分之百復原指令重排的例子。

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;
        }
    }
}
1、結果預測

reader方法僅在flag變量為true時向控制臺打印變量a的值。

writer方法先執行變量a的賦值操作,后執行變量flag的賦值操作。

如果按照上述分析邏輯,那么控制臺打印的結果一定全為1。

2、指令重排

假如代碼未發生指令重排,那么當flag變量為true時,變量a一定為1。

上述代碼中關于變量a和變量flag在兩個方法類均存在指令重排的情況。

public static void writer() {
    a = 1;
    flag = true;
}

通過觀察日志輸出,發現有大量的0輸出。

writer方法內部發生指令重排時,flag變量先完成賦值,此時假如當前線程發生中斷,其它線程在調用reader方法,檢測到flag變量為true,那么便打印變量a的值。此時控制臺存在超出期望值的結果。

(二)new創建對象

使用關鍵字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;
}
1、解析創建過程
  • 使用關鍵字new創建一個對象,大致分為一下過程:

  • 在棧空間創建引用地址

  • 以類文件為模版在堆空間對象分配內存

  • 成員變量初始化

  • 使用構造函數初始化

  • 將引用值賦值給左側存儲變量

2、重排序過程分析

針對上述示例,假設第一個線程進入synchronized代碼塊,并開始創建對象,由于重排序存在,正常的創建對象過程被打亂,可能會出現在棧空間創建引用地址后,將引用值賦值給左側存儲變量,隨后因CPU調度時間片耗盡而產生中斷的情況。

后續線程在檢測到instance變量不為空,則直接使用。因為單例對象并為實例化完成,直接使用會帶來意想不到的結果。

三、應對指令重排

(一)AtomicReference原子類

使用原子類將一組相關聯的變量封裝成一個對象,利用原子操作的特性,有效回避指令重排問題。

@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;
}

四、指令重排的理解

1、指令重排廣泛存在

指令重排不僅限于Java程序,實際上各種編譯器均有指令重排的操作,從軟件到CPU硬件都有。指令重排是對單線程執行的程序的一種性能優化,需要明確的是,指令重排在單線程環境下,不會改變順序程序執行的預期結果。

2、多線程環境指令重排

上面討論了兩種典型多線程環境下指令重排,分析其帶來負面影響,并分別提供了應對方式。

  • 對于關聯變量,先封裝成一個對象,然后使用原子類來操作

  • 對于new對象,使用volatile關鍵字修飾目標對象即可

3、synchronized鎖與重排序無關

synchronized鎖通過互斥鎖,有序的保證線程訪問特定的代碼塊。代碼塊內部的代碼正常按照編譯器執行的策略重排序。

盡管synchronized鎖能夠回避多線程環境下重排序帶來的不利影響,但是互斥鎖帶來的線程開銷相對較大,不推薦使用。

synchronized 塊里的非原子操作依舊可能發生指令重排

“Java指令重排在多線程環境下怎么解決”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

中阳县| 大荔县| 旺苍县| 繁昌县| 尉氏县| 枞阳县| 哈密市| 锦屏县| 洞头县| 明星| 德格县| 华安县| 鹤岗市| 阿瓦提县| 信阳市| 宁强县| 金山区| 南川市| 望奎县| 南溪县| 咸阳市| 响水县| 东台市| 诸暨市| 旌德县| 嘉善县| 平顺县| 宝坻区| 浦东新区| 稷山县| 商都县| 科尔| 墨竹工卡县| 城步| 都兰县| 苍溪县| 福清市| 崇文区| 新宁县| 宁武县| 乃东县|