中文字幕精品亚洲无线码二区,国产黄a三级三级三级看三级,亚洲七七久久桃花影院,丰满少妇被猛烈进入,国产小视频在线观看网站

沒有Happens-Before?你的多線程代碼就是‘一鍋粥’!

內存模型與happens-before:開發者與硬件的和平條約

在前文中,提到處理器通過一些特殊指令(如 LOCK、CMPXCHG、內存屏障等)來保障多線程環境下程序的正確性。然而,這種做法仍然存在幾個顯著問題。
1)底層指令實現復雜且晦澀:處理器指令的細節往往難以理解,開發者需要付出大量的時間和精力來掌握這些低級實現。
2)不同平臺間的兼容性問題:不同硬件架構和操作系統對這些指令的支持和實現方式各不相同,這要求程序在設計時考慮到跨平臺的兼容性和一致性。
3)多線程數據操作的復雜性:隨著程序業務邏輯的多變,處理器與線程之間的內存訪問依賴關系變得更加復雜,從而增加了程序出錯的風險。
為了簡化并發編程,解決這些問題,現代編程語言通常提供了抽象的內存模型,用以規范多線程環境下的內存訪問行為。這種抽象使開發者無需關注底層硬件與操作系統實現細節,即可編寫高效且可移植的并發程序。
以 Java 為例,Java語言采用了Java 內存模型(Java Memory Model,JMM)來提供這種抽象。 Java 內存模型的核心目的是通過支持諸如 volatile、synchronized、final 等同步原語,來確保在多線程環境下程序的原子性、可見性和有序性。這些原語確保了不同線程間的操作能夠按照特定的規則正確協作。
此外,JMM 還引入了一個重要概念:happens-before 關系,旨在描述并發編程中操作之間的偏序關系。具體來說,偏序關系主要用于確保線程間操作的順序性,避免因執行順序不明確而導致的并發問題。
偏序關系在并發編程中的應用主要體現在以下兩種情況。
1)程序順序(Program Order):指單線程中,由程序控制流決定的操作順序。例如,如果操作 A 在操作 B 之前執行,那么我們可以認為 A <= B。
2)同步順序(Synchronization Order):指由并發控制機制(如鎖、信號量等)所控制的操作順序。例如,如果操作 A 釋放了鎖,而操作 B 隨后獲取了該鎖,那么我們可以認為 A <= B。
除了 Java 之外,其他現代編程語言,如 Go、C++、Rust 等,也都實現了各自的 happens-before 關系機制,以確保并發程序的正確性和一致性。
image

Java內存模型的happens-before關系是用來描述兩個線程操作的內存可見性。需注意這里的可見性,并不代表A線程的執行順序一定早于B線程, 而是A線程對某個共享變量的操作對B線程可見。即A線程對變量a進行寫操作,B線程讀取到是變量a的變更值。
Java內存模型定義了主內存(Main memory),本地內存(Local memory),共享變量等抽象關系,來決定共享變量在多線程之間通信同步方式,即前面所說兩個線程操作的內存可見性。其中本地內存,涵蓋了緩存,寫緩沖區,寄存器以及其他硬件和編譯器優化等概念。
image

如圖所示,如果線程A與線程B之間要通信的話,必須要經歷下面2個步驟:
1)線程A把本地內存A中更新過的共享變量刷新到主內存中;
2)線程B到主內存中去讀取線程A之前已更新過的共享變量。
為了進一步抽象這種線程間的數據同步方式,Java內存模型定義了下述線程間的happens-before關系。
1)程序順序規則:單線程內(nei),每個操(cao)作(zuo)(zuo)happens-before于(yu)該(gai)線程中的任意(yi)后續操(cao)作(zuo)(zuo)。

// Thread1內, A happens-before B,B happens-before C。
// 這意味著A一定會在B之前完成,B一定會在C之前完成。因此,可以確信y包含x+5的結果。
Thread1 {
    x = 10;    // A
    y = x + 5; // B
    print(y);  // C
}

2)監視器鎖(suo)(suo)規則(ze):釋放(fang)鎖(suo)(suo)的操作happens-before之后對同(tong)一把鎖(suo)(suo)的獲(huo)取的鎖(suo)(suo)操作。

// Thread1釋放鎖(B)happens-before Thread2獲取鎖(C)。
// 這意味著當Thread2打印x時,它看到的一定是"Thread1 data",因為Thread1的修改操作和釋放鎖操作,都在Thread2獲取鎖之前完成。
lock = Lock() 

Thread1 {
    lock.acquire() 
    x = "Thread1 data" // A
    lock.release() // B
}

Thread2 {
    lock.acquire() // C
    print(x) // D
    lock.release() 
}

3)volatile變量規則:volatile字段的寫操作(zuo)happens-before之后對同一字段的讀操作(zuo)。

// 對volatile字段的寫操作(A)happens-before之后對同一字段的讀操作(B)。
// 這意味著當Thread2讀取x時,它看到的一定是100,因為Thread1的寫操作在Thread2的讀操作之前完成。
volatile int sharedData; // 聲明一個volatile變量

Thread1 {
    x = 100; // A
}

Thread2 {
    int localData = x; // B
    print(localData);
}

4)傳遞(di)性規則:如果(guo)A happens-before B,且B happens-before C,那么A happens-before C。

// 如果Thread1 A happens-before Thread2 B,且Thread2 B happens-before Thread2 C,那么Thread1 A happens-before Thread2 C。
// 這意味著A一定會在C之前完成。因此,可以確信z包含(x+5)*2的結果,因為賦值給x的操作和計算x+5的操作都在計算y*2的操作之前完成。
Thread1 {
    x = 10;    // A
}

Thread2 {
    y = x + 5; // B
    z = y * 2; // C
    print(z);  
}

5)start()規(gui)則:如果線程A執行操(cao)作(zuo)ThreadB.start(),那么(me)A線程的ThreadB.start()操(cao)作(zuo)happens-before于線程B中(zhong)的任意操(cao)作(zuo)。

// 如果Thread1執行操作ThreadB.start(),那么Thread1 A happens-before Thread2 C。
// 這意味著A一定會在C之前完成。因此,可以確信Thread1 x值為10,因為賦值給x的操作在打印x的操作之前完成。
Thread1 {
    ThreadB.start(); // A
    x = 10; // B
}

Thread2 {
    print(x); // C
}

6)join()規則:如果(guo)線(xian)(xian)程A執行操作ThreadB.join()并成(cheng)功返(fan)回(hui)(hui),那(nei)么線(xian)(xian)程B中的任意操作 happens-before 線(xian)(xian)程A從ThreadB.join()操作成(cheng)功返(fan)回(hui)(hui)。

// 如果線Thread1執行操作Thread2.join(),那么Thread2 D happens-before Thread1 C。
// 這意味著D一定會在C之前完成。因此,可以確信Thread1 x值為10,因為賦值給x的操作在打印x的操作之前完成。
Thread1 {
    Thread2.start(); // A
    Thread2.join();  // B
    print(x);        // C
}

Thread2 {
    x = 10;          // D
}

如上的happens-before關系中(zhong)(zhong),與日常開發(fa)密切相關的是1、2、3、4四(si)個規(gui)則(ze)。其(qi)中(zhong)(zhong)規(gui)則(ze)1滿足了as-if-serial語義(yi),即Java內存模型允許代碼(ma)和(he)指令重排(pai)序,只(zhi)要不(bu)影響程(cheng)序執行結(jie)果(guo)。規(gui)則(ze)2和(he)3是通過synchronized、volatile關鍵字實現。結(jie)合規(gui)則(ze)1、2、3來(lai)看看規(gui)則(ze)4的具體使用,可以看到(dao)如下的代碼(ma),程(cheng)序最終執行且得到(dao)正確結(jie)果(guo)。

// x, y, z被volatile關鍵字修飾
private volatile int x, y, z; 
    
public void test() {
    Thread a = new Thread(() -> {
        // 基于程序順序規則
        // 沒有數據依賴關系,可以重排序下面代碼 
        int i = 0;
        x = 2;
        // 基于volatile變量規則
        // 編譯器插入storeload內存屏障指令 
        // 1)禁止代碼和指令重排序
        // 2)強制刷新變量x的最新值到內存
    });
        
    Thread b = new Thread(() -> {
        int i = 0;
        // 存在數據依賴關系,無法重排序下面代碼
        // 強制從主內存中讀取變量x的最新值
        y = x;
        // 基于volatile變量規則
        // 編譯器插入storeload內存屏障指令
        // 1)禁止代碼和指令重排序
        // 2)強制刷新變量y的最新值到內存
        // 3)y = x;可能會被編譯優化去除
        y = 3;
        // 編譯器插入storeload內存屏障指令
        // 1)禁止代碼和指令重排序
        // 2)強制刷新變量y的最新值到內存
    });
        
    Thread c = new Thread(() -> {
        // 基于程序順序規則
        // 沒有數據依賴關系,可以重排序下面代碼
        int i = 0;
        // 基于volatile變量規則
        // 強制從主內存中讀取變量x和y的最新值
        z = x * y;
        // 編譯器插入storeload內存屏障指令
        // 1)禁止代碼和指令重排序
        // 2)強制刷新變量z的最新值到內存
    });
        
    // ...start啟動線程,join等待線程
    assert z == 6;
    // 可以看到a線程對變量x變更,b線程對變量y變更,最終對線程c可見
    // 即滿足傳遞性規則
}
private int x, y, z;

public void test() {
    Thread a = new Thread(() -> {
       // synchronized,同步原語,程序邏輯將順序串行執行
        synchronized (this){
            // 基于程序順序規則
            // 沒有數據依賴關系,可以重排序下面代碼
            int i = 0;
            x = 2;
            // 基于監視器鎖規則
            // 強制刷新變量x的最新值到內存
        }
    });
        
    Thread b = new Thread(() -> {
       // synchronized,同步原語,程序邏輯將順序串行執行
        synchronized (this) {
            int i = 0;
            // 存在數據依賴關系,無法重排序下面代碼
            // 強制從主內存中讀取變量x的最新值
            y = x;
            // 基于監視器鎖規則
            // 1)強制刷新變量y的最新值到內存
            // 2)y = x;可能會被編譯優化去除
           y = 3;
            // 強制刷新變量y的最新值到內存
        }
    });
        
    Thread c = new Thread(() -> {
       // synchronized,同步原語,程序邏輯將順序串行執行
        synchronized (this) {
            // 基于程序順序規則
            // 沒有數據依賴關系,可以重排序下面代碼
            int i = 0;
            // 基于監視器鎖規則
            // 強制從主內存中讀取變量x和y的最新值
            z = x * y;
            // 強制刷新變量z的最新值到內存
        }
    });
        
    // ...start啟動線程,join等待線程
    assert z == 6;
    // 可以看到a線程對變量x變更,b線程對變量y變更,最終對線程c可見
    // 即滿足傳遞性規則
}

總結:在混沌與秩序間搭建橋梁
Java內存模型是并發編程中連接開發者與硬件系統的關鍵橋梁。它依托可見性、有序性和原子性這三大核心原則,將復雜的并發問題轉化為清晰的編程規范。當多個線程操作共享變量時,Java內存模型利用volatile、synchronized等機制,有效抑制了處理器優化帶來的不確定性,同時兼顧了性能優化需求。其定義的happens-before關系,如同線程間的通信準則,以順序性規則替代了對緩存刷新、指令重排等底層操作的直接操控。這種設計讓開發者能夠專注于業務邏輯,僅憑有限的同步手段就能構建出穩健的多線程程序。
Java內存模型的價值(zhi)在于達成了三個(ge)重要平衡:它確保程序正確性(xing)不依賴于硬件實現細(xi)節;維持(chi)同步規則的簡(jian)潔性(xing)以(yi)控(kong)制復雜度;讓開發者能以(yi)較低的認知成本(ben)構建并(bing)發系統。這無疑是工程解耦的典范:用簡(jian)潔的抽象(xiang)來掌控(kong)復雜的世界。

很高興與你相遇!

如果你喜歡本文內容,記得關注哦!!!

posted on 2025-07-30 14:46  poemyang  閱讀(573)  評論(0)    收藏  舉報

導航