最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

深入淺出Java中的Happens-Before核心規(guī)則

 更新時間:2025年09月23日 14:44:40   作者:碼熔burning  
本文解析Java內(nèi)存模型中的Happens-Before原則,解釋其定義、核心規(guī)則及實際應(yīng)用,幫助理解多線程可見性與有序性問題,掌握并發(fā)編程關(guān)鍵,感興趣的朋友跟隨小編一起看看吧

前言

在Java并發(fā)編程中,我們經(jīng)常會遇到這樣的問題:多線程環(huán)境下,一個線程對共享變量的修改,另一個線程能看到嗎?為什么有時候明明修改了變量,其他線程卻讀取到舊值?這些問題的答案,都與Java內(nèi)存模型(JMM)中的Happens-Before原則密切相關(guān)。

本文將從基礎(chǔ)概念出發(fā),循序漸進地解析Happens-Before的定義、核心規(guī)則及實際應(yīng)用,幫助你徹底理解這一Java并發(fā)編程的基石。

一、Happens-Before是什么?為什么需要它?

1.1 從一個問題說起

先看一段簡單的代碼:

// 線程A執(zhí)行
int x = 1;  // 操作A
boolean flag = true;  // 操作B
// 線程B執(zhí)行
if (flag) {  // 操作C
    System.out.println(x);  // 操作D
}

如果線程A和線程B并發(fā)執(zhí)行,線程D會打印出1嗎?

直覺上應(yīng)該會,但實際上可能不會。因為在多線程環(huán)境中,編譯器優(yōu)化、CPU指令重排序、緩存等機制可能導(dǎo)致:

  • 線程A中,操作A和B的執(zhí)行順序可能被調(diào)換(重排序)
  • 線程A修改的x和flag可能還停留在CPU緩存中,未同步到主內(nèi)存
  • 線程B可能讀取的是主內(nèi)存中未更新的舊值

這些問題會導(dǎo)致可見性有序性問題。而Happens-Before原則就是JMM為解決這些問題提出的核心規(guī)范。

1.2 Happens-Before的定義

Happens-Before是JMM中定義的兩個操作之間的偏序關(guān)系
如果操作A Happens-Before操作B,那么A的執(zhí)行結(jié)果必須對B可見,且A的執(zhí)行順序在邏輯上先于B。

注意

  • Happens-Before不代表“物理時間上的先后”,而是JMM定義的“邏輯先行”關(guān)系。
  • 即使A在物理時間上后于B執(zhí)行,只要A Happens-Before B,A的結(jié)果就必須對B可見。

1.3 為什么需要Happens-Before?

JMM的核心目標是:在保證并發(fā)程序正確性的前提下,盡可能為編譯器和CPU的優(yōu)化留出空間

Happens-Before的價值在于:

  • 它屏蔽了底層硬件和編譯器的復(fù)雜細節(jié)(如重排序、緩存等),為開發(fā)者提供了簡單清晰的可見性判斷標準。
  • 它允許編譯器和CPU在不違反Happens-Before規(guī)則的前提下進行優(yōu)化,保證性能。

簡單說:開發(fā)者只需關(guān)注是否滿足Happens-Before規(guī)則,無需關(guān)心底層如何實現(xiàn)可見性;JVM則需保證在滿足規(guī)則的情況下,底層優(yōu)化不破壞可見性

二、Happens-Before的核心規(guī)則

JMM定義了8條核心的Happens-Before規(guī)則,這些規(guī)則是判斷可見性的基礎(chǔ)。我們逐一解析:

規(guī)則1:程序順序規(guī)則(Program Order Rule)

在同一個線程中,按照代碼順序,前面的操作Happens-Before后面的操作。

例如:

// 單線程內(nèi)
int a = 1;  // 操作A
int b = a + 1;  // 操作B

根據(jù)程序順序規(guī)則,A Happens-Before B。因此:

  • 操作A的結(jié)果(a=1)對操作B可見
  • 邏輯上A先于B執(zhí)行(即使編譯器可能重排序,但會保證結(jié)果等價于順序執(zhí)行)

規(guī)則2:監(jiān)視器鎖規(guī)則(Monitor Lock Rule)

對一個鎖的解鎖操作Happens-Before后續(xù)對同一個鎖的加鎖操作。

synchronized是Java中最典型的監(jiān)視器鎖,例如:

synchronized (lock) {  // 加鎖
    // 臨界區(qū)操作A
    x = 1;
}  // 解鎖(操作U)
// 其他線程
synchronized (lock) {  // 加鎖(操作L)
    // 臨界區(qū)操作B
    System.out.println(x);  // 必然打印1
}

根據(jù)規(guī)則:
解鎖操作U Happens-Before 后續(xù)的加鎖操作L,因此操作A的結(jié)果(x=1)對操作B可見。

這就是synchronized能保證可見性的底層原因。

規(guī)則3:volatile變量規(guī)則(Volatile Variable Rule)

對volatile變量的寫操作Happens-Before后續(xù)對同一個volatile變量的讀操作。

例如:

// 線程A
volatile int x = 0;
x = 1;  // 寫操作W
// 線程B
int y = x;  // 讀操作R

根據(jù)規(guī)則:W Happens-Before R,因此線程B讀取到的y必然是1(而非0)。

原理
volatile變量的寫操作會強制將緩存中的值刷新到主內(nèi)存,讀操作會強制從主內(nèi)存加載最新值,且禁止了volatile變量前后操作的重排序,從而保證可見性。

規(guī)則4:線程啟動規(guī)則(Thread Start Rule)

主線程對Thread對象的start()方法調(diào)用Happens-Before子線程中的所有操作。

例如:

// 主線程
int x = 1;
Thread t = new Thread(() -> {
    // 子線程操作
    System.out.println(x);  // 必然打印1
});
t.start();  // 啟動操作S

根據(jù)規(guī)則:S Happens-Before子線程中的打印操作,因此主線程在start()前對x的修改(x=1)對sub線程可見。

規(guī)則5:線程終止規(guī)則(Thread Termination Rule)

子線程中的所有操作Happens-Before主線程檢測到子線程終止

主線程可以通過join()、isAlive()等方法檢測子線程是否終止,例如:

// 主線程
Thread t = new Thread(() -> {
    // 子線程操作A
    x = 1;
});
t.start();
t.join();  // 等待子線程終止(操作J)
System.out.println(x);  // 必然打印1

根據(jù)規(guī)則:子線程的操作A Happens-Before 主線程的操作J,因此主線程在join()后能看到x=1。

規(guī)則6:線程中斷規(guī)則(Thread Interruption Rule)

對線程interrupt()方法的調(diào)用Happens-Before被中斷線程檢測到中斷事件。

例如:

// 線程A
Thread t = new Thread(() -> {
    // 子線程
    if (Thread.interrupted()) {  // 檢測中斷(操作C)
        System.out.println("被中斷");
    }
});
t.start();
t.interrupt();  // 中斷操作I

根據(jù)規(guī)則:I Happens-Before C,因此子線程能檢測到中斷事件。

規(guī)則7:傳遞性規(guī)則(Transitivity)

如果A Happens-Before B,且B Happens-Before C,那么A Happens-Before C

這是非常重要的一條規(guī)則,它可以將多個Happens-Before關(guān)系串聯(lián)起來,例如:

// 線程A
x = 1;  // A
volatile boolean flag = true;  // B
// 線程B
if (flag) {  // C(讀volatile)
    int y = x;  // D
}
  • 根據(jù)程序順序規(guī)則:A Happens-Before B,C Happens-Before D
  • 根據(jù)volatile規(guī)則:B Happens-Before C
  • 根據(jù)傳遞性:A Happens-Before D → 因此D能看到x=1

規(guī)則8:對象終結(jié)規(guī)則(Finalizer Rule)

對象的構(gòu)造函數(shù)執(zhí)行完畢Happens-Before其finalize()方法開始執(zhí)行

確保對象在被回收前,其構(gòu)造函數(shù)的所有初始化操作都已完成,例如:

class MyObject {
    int x;
    MyObject() {
        x = 1;  // 構(gòu)造函數(shù)操作A
    }
    @Override
    protected void finalize() {
        System.out.println(x);  // 必然打印1(操作B)
    }
}

根據(jù)規(guī)則:A Happens-Before B,因此finalize()中能看到構(gòu)造函數(shù)對x的初始化。

三、Happens-Before與重排序的關(guān)系

JMM允許編譯器和CPU進行重排序優(yōu)化,但有一個前提:重排序不能破壞Happens-Before規(guī)則

例如,在單線程中:

int a = 1;  // A
int b = 2;  // B
int c = a + b;  // C

編譯器可能將A和B重排序(先執(zhí)行B再執(zhí)行A),但由于A和B之間沒有數(shù)據(jù)依賴,且重排序后C的結(jié)果仍為3,因此這種重排序是允許的——它沒有破壞程序順序規(guī)則的Happens-Before關(guān)系(A和B的執(zhí)行順序不影響最終結(jié)果的可見性)。

但如果是:

int a = 1;  // A
int b = a;  // B

A和B存在數(shù)據(jù)依賴(B依賴A的結(jié)果),編譯器不能重排序A和B,否則會破壞程序順序規(guī)則的Happens-Before關(guān)系(B可能讀取到a的舊值)。

四、不滿足Happens-Before的情況:可見性問題

如果兩個操作之間不存在任何Happens-Before規(guī)則,JMM無法保證它們的可見性,可能出現(xiàn)“臟讀”。

例如:

// 線程A
int x = 1;  // A
// 線程B
System.out.println(x);  // B

A和B之間沒有任何Happens-Before關(guān)系(不滿足上述8條規(guī)則中的任何一條),因此:

  • 線程B可能打印1(x已同步到主內(nèi)存)
  • 也可能打印0(x仍在A的CPU緩存中,未同步)

這種情況下,結(jié)果是不確定的,這就是多線程編程中“可見性問題”的根源。

五、Happens-Before的實際應(yīng)用

Happens-Before是理解Java并發(fā)工具的基礎(chǔ),以下是幾個典型場景:

5.1 synchronized與Happens-Before

synchronized通過“解鎖-加鎖”的Happens-Before關(guān)系保證可見性:

// 線程A
synchronized (lock) {
    x = 1;  // 解鎖前的操作
}  // 解鎖U
// 線程B
synchronized (lock) {  // 加鎖L(U Happens-Before L)
    System.out.println(x);  // 可見x=1
}

5.2 volatile與Happens-Before

volatile通過“寫-讀”的Happens-Before關(guān)系保證可見性:

// 線程A
volatile boolean ready = false;
int data = 0;
data = 1;  // A
ready = true;  // B(寫volatile)
// 線程B
if (ready) {  // C(讀volatile,B Happens-Before C)
    System.out.println(data);  // D(A Happens-Before D,因此可見1)
}

這里結(jié)合了程序順序規(guī)則(A Happens-Before B)、volatile規(guī)則(B Happens-Before C)和傳遞性規(guī)則(A Happens-Before D)。

5.3 ConcurrentHashMap與Happens-Before

ConcurrentHashMap的put操作與get操作之間存在Happens-Before關(guān)系:

  • 線程A的put(k, v)操作Happens-Before線程B的get(k)操作
  • 因此線程B的get(k)能看到線程Aput的v值

這是ConcurrentHashMap保證線程安全的底層基礎(chǔ)之一。

六、常見面試問題

  • Happens-Before的定義是什么?
    • 它是JMM中定義的兩個操作之間的偏序關(guān)系:如果A Happens-Before B,則A的結(jié)果對B可見,且A的邏輯執(zhí)行順序先于B。
  • Happens-Before和物理時間順序有什么區(qū)別?
    • 無關(guān)。Happens-Before是邏輯先行關(guān)系,與物理時間上的先后無關(guān)。即使A在物理時間上后于B執(zhí)行,只要A Happens-Before B,A的結(jié)果就必須對B可見。
  • volatile變量的寫操作和讀操作之間有什么Happens-Before關(guān)系?
    • 對volatile變量的寫操作Happens-Before后續(xù)對該變量的讀操作,這保證了volatile變量的可見性。
  • 如何利用Happens-Before規(guī)則判斷多線程操作的可見性?
    • 只要兩個操作之間存在通過Happens-Before規(guī)則(直接或間接)建立的關(guān)系,就可以保證可見性;否則,可見性無法保證。

七、總結(jié)

Happens-Before是Java內(nèi)存模型的核心,它為開發(fā)者提供了判斷多線程操作可見性的清晰標準。理解Happens-Before,你就能:

  • 明白為什么synchronizedvolatile等關(guān)鍵字能保證可見性
  • 避免多線程編程中的“臟讀”“不可見”等問題
  • 更深入地理解Java并發(fā)工具(如ConcurrentHashMap、AQS)的底層原理

記?。?strong>Happens-Before的本質(zhì)是“可見性契約”——JMM通過它承諾,只要滿足規(guī)則,就保證操作結(jié)果的可見性。掌握這一原則,是成為Java并發(fā)編程高手的關(guān)鍵一步。

到此這篇關(guān)于深入淺出Java中的Happens-Before核心規(guī)則的文章就介紹到這了,更多相關(guān)java Happens-Before內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java讀取資源路徑的幾種實現(xiàn)方式

    java讀取資源路徑的幾種實現(xiàn)方式

    文章總結(jié)了Java讀取資源路徑的幾種方式,并指出了在JUnit測試文件和普通類中讀取資源路徑的區(qū)別,普通類中讀取資源路徑時,只返回主目錄,而JUnit測試文件中可以精確到所在模塊
    2025-02-02
  • Nacos注冊中心的部署與用法示例詳解

    Nacos注冊中心的部署與用法示例詳解

    注冊中心是微服務(wù)架構(gòu)中的紐帶,類似于“通訊錄”,它記錄了服務(wù)和服務(wù)地址的映射關(guān)系,本文通過示例代碼給大家介紹Nacos注冊中心的部署與用法,感興趣的朋友跟隨小編一起看看吧
    2022-02-02
  • java 獲取已知文件擴展名的代碼

    java 獲取已知文件擴展名的代碼

    java 編寫程序獲取已知文件的擴展名. 注意: abc.txt的擴展名是txt, abc.java.txt的擴展名也是txt.,需要的朋友可以參考下
    2017-02-02
  • java集合進行排序的方式總結(jié)

    java集合進行排序的方式總結(jié)

    在本篇文章里小編給大家整理的是一篇關(guān)于java集合進行排序的兩種方式總結(jié),有興趣的朋友們可以學(xué)習(xí)參考下。
    2021-08-08
  • 全面了解java byte數(shù)組與文件讀寫

    全面了解java byte數(shù)組與文件讀寫

    下面小編就為大家?guī)硪黄媪私鈐ava byte數(shù)組與文件讀寫。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-08-08
  • Java新手教程之ArrayList的基本使用

    Java新手教程之ArrayList的基本使用

    ArrayList就是傳說中的動態(tài)數(shù)組,用MSDN中的說法,就是Array的復(fù)雜版本,這篇文章主要給大家介紹了關(guān)于Java新手教程之ArrayList基本使用的相關(guān)資料
    2021-06-06
  • SpringBoot詳解整合MyBatis過程中可能遇到的問題

    SpringBoot詳解整合MyBatis過程中可能遇到的問題

    因為Spring Boot框架開發(fā)的便利性,所以實現(xiàn)Spring Boot與數(shù)據(jù)訪問層框架(例如MyBatis)的整合非常簡單,主要是引入對應(yīng)的依賴啟動器,并進行數(shù)據(jù)庫相關(guān)參數(shù)設(shè)置即可
    2022-07-07
  • Java實現(xiàn)簡單局域網(wǎng)聊天室

    Java實現(xiàn)簡單局域網(wǎng)聊天室

    這篇文章主要為大家詳細介紹了Java實現(xiàn)簡單局域網(wǎng)聊天室,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-06-06
  • IDEA中為SpringBoot項目接入MySQL數(shù)據(jù)庫的詳細指南

    IDEA中為SpringBoot項目接入MySQL數(shù)據(jù)庫的詳細指南

    MySQL作為最流行的開源關(guān)系型數(shù)據(jù)庫,與Spring Boot的整合是企業(yè)級開發(fā)的標配,本文將手把手教你?在IntelliJ IDEA中為Spring Boot項目接入MySQL數(shù)據(jù)庫?,有需要的可以了解下
    2025-05-05
  • Java緩存ehcache的使用步驟

    Java緩存ehcache的使用步驟

    這篇文章主要介紹了Java緩存ehcache的使用步驟,文中有非常詳細的代碼示例,對正在學(xué)習(xí)java的小伙伴們有很好的幫助,需要的朋友可以參考下
    2021-05-05

最新評論

辉南县| 宁强县| 郴州市| 南陵县| 紫云| 阿拉善左旗| 博乐市| 平陆县| 广宁县| 湟源县| 汶川县| 布尔津县| 高唐县| 泰宁县| 三亚市| 梁平县| 扎鲁特旗| 无棣县| 上栗县| 孝感市| 南阳市| 乐陵市| 彝良县| 平乐县| 林口县| 土默特左旗| 开原市| 南投市| 巴彦淖尔市| 中山市| 岫岩| 绥阳县| 彰化市| 彰武县| 策勒县| 吴桥县| 屏南县| 安义县| 扬州市| 柘城县| 台山市|