Java併發(2)- 聊聊happens-before

来源:https://www.cnblogs.com/konck/archive/2018/07/19/9330702.html
-Advertisement-
Play Games

引言 上一篇文章聊到了Java記憶體模型,在其中我們說JMM是建立在happens before(先行發生)原則之上的。 為什麼這麼說呢?因為在Java程式的執行過程中,編譯器和處理器對我們所寫的代碼進行了一系列的優化來提高程式的執行效率。這其中就包括對指令的“重排序”。 重排序導致了我們代碼並不會按 ...


引言

上一篇文章聊到了Java記憶體模型,在其中我們說JMM是建立在happens-before(先行發生)原則之上的。
為什麼這麼說呢?因為在Java程式的執行過程中,編譯器和處理器對我們所寫的代碼進行了一系列的優化來提高程式的執行效率。這其中就包括對指令的“重排序”。
重排序導致了我們代碼並不會按照代碼編寫順序來執行,那為什麼我們在程式執行後結果沒有發生錯亂,原因就是Java記憶體模型遵循happens-before原則。在happens-before規則下,不管程式怎麼重排序,執行結果不會發生變化,所以我們不會看到程式結果錯亂。

重排序

重排序是什麼?通俗點說就是編譯器和處理器為了優化程式執行性能對指令的執行順序做了一定修改。
重排序會發生在程式執行的各個階段,包括編譯器沖排序、指令級並行沖排序和記憶體系統重排序。這裡不具體分析每個重排序的過程,只要知道重排序導致我們的代碼並不會按照我們編寫的順序來執行。
在單線程的的執行過程中發生重排序後我們是無法感知的,如下代碼所示,

int a = 1;  //步驟1
int b = 2;  //步驟2
int c = a + b; //步驟3 

1和2做了重排序並不會影響程式的執行結果,在某些情況下為了優化性能可能會對1和2做重排序。2和3的重排序會影響執行結果,所以編譯器和處理器不會對2和3進行重排序。
在多線程中如果沒有進行正確的同步,發生重排序我們是可以感知的,比如下麵的代碼:

public class AAndB {

    int x = 0;
    int y = 0;
    int a = 0;
    int b = 0;
    
    public void awrite() {

        a = 1;
        x = b;
    }
    
    public void bwrite() {

        b = 1;
        y = a;
    }
}

public class AThread extends Thread{

    private AAndB aAndB;
    
    public AThread(AAndB aAndB) {
        
        this.aAndB = aAndB;
    }
    
    @Override
    public void run() {
        super.run();
        
        this.aAndB.awrite();
    }
}

public class BThread extends Thread{

    private AAndB aAndB;
    
    public BThread(AAndB aAndB) {
        
        this.aAndB = aAndB;
    }
    
    @Override
    public void run() {
        super.run();
        
        this.aAndB.bwrite();
    }
}

private static void testReSort() throws InterruptedException {

    AAndB aAndB = new AAndB();

    for (int i = 0; i < 10000; i++) {
        AThread aThread = new AThread(aAndB);
        BThread bThread = new BThread(aAndB);

        aThread.start();
        bThread.start();

        aThread.join();
        bThread.join();

        if (aAndB.x == 0 && aAndB.y == 0) {
            System.out.println("resort");
        }

        aAndB.x = aAndB.y = aAndB.a = aAndB.b = 0;

    }

    System.out.println("end");
}

如果不進行重排序,程式的執行順序有四種可能:




但程式在執行多次後會列印出“resort”,這種情況就說明瞭A線程和B線程都出現了重排序。

happens-before的定義

happens-before定義了八條規則,這八條規則都是用來保證如果A happens-before B,那麼A的執行結果對B可見且A的執行順序排在B之前。

  1. 程式次序規則:在一個單獨的線程中,按照程式代碼的執行流順序,(時間上)先執行的操作happen—before(時間上)後執行的操作。
  2. 管理鎖定規則:一個unlock操作happen—before後面(時間上的先後順序,下同)對同一個鎖的lock操作。
  3. volatile變數規則:對一個volatile變數的寫操作happen—before後面對該變數的讀操作。
  4. 線程啟動規則:Thread對象的start()方法happen—before此線程的每一個動作。
  5. 線程終止規則:線程的所有操作都happen—before對此線程的終止檢測,可以通過Thread.join()方法結束、Thread.isAlive()的返回值等手段檢測到線程已經終止執行。
  6. 線程中斷規則:對線程interrupt()方法的調用happen—before發生於被中斷線程的代碼檢測到中斷時事件的發生。
  7. 對象終結規則:一個對象的初始化完成(構造函數執行結束)happen—before它的finalize()方法的開始。
  8. 傳遞性:如果操作A happen—before操作B,操作B happen—before操作C,那麼可以得出A happen—before操作C。

happens-before定義了這麼多規則,其實總結起來可以歸納為一句話:happens-before規則保證了單線程和正確同步的多線程的執行結果不會被改變。
那為什麼有程式次序規則的保證,上面多線程執行過程中還是出現了重排序呢?這是因為happens-before規則僅僅是java記憶體模型向程式員做出的保證。在單線程下,他並不關心程式的執行順序,只保證單線程下程式的執行結果一定是正確的,java記憶體模型允許編譯器和處理器在happens-before規則下對程式的執行做重排序。
而且從程式員角度來說,對於兩個操作是否真的被重排序並不關心,關心的是程式執行結果是否被改變。
上面的程式在單線程會被重排序的情況下又沒有對多線程同步,這樣就導致了意料之外的結果。

as-if-serial語義

《Java併發編程的藝術》中解釋:

as-if-serial就是不管怎麼重排序(編譯器和處理器為了提高並行度),(單線程)程式的執行結果不能被改變。編譯器、runtime和處理器都必須遵守as-if-serial語義。

這句話通俗理解就是as-if-serial語義保證單線程程式的執行結果不會被改變。
本質上和happens-before規則是一個意思:happens-before規則保證了單線程和正確同步的多線程的執行結果不會被改變。都是對執行結果做保證,對執行過程不做保證。
這也是JMM設計上的一個亮點:既保證了程式員編程時的方便以及正確,又同時保證了編譯器和處理器更大限度的優化自由。


參考資料:
《深入理解Java記憶體模型》
《深入理解Java虛擬機》
《Java併發編程的藝術》


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • MDN中FileReader的詳細介紹: https://developer.mozilla.org/zh-CN/docs/Web/API/FileReader 用FileReader獲取圖片base64,併在頁面預覽: ...
  • 效果如下:點擊發送驗證碼按鈕,按鈕背景變色,不可點擊,顯示倒計時文字 首先js文件的data裡面 聲明一個變數用於表示當前是否可以點擊,codeIsCanClick = true, 預設是可以點擊的 寫下界面代碼: wxml文件中 對應樣式 wxss文件: 以上構成頁面靜態效果。 註意button有 ...
  • 一、關於樣式(CSS) 1、Input 1)Input可編輯可下拉 2)Input下拉 3)Input邊線點擊不顯示 input點擊邊框樣式無效 4)提示文字:placeholder="手機號" input修改提示文字顏色 5)input出現背景是黃色 這種點擊框也不會出現黃色了 還有一種就是關閉自 ...
  • 前言 秒殺架構到後期,我們採用了消息隊列的形式實現搶購邏輯,那麼之前拋出過這樣一個問題:消息隊列非同步處理完每個用戶請求後,如何通知給相應用戶秒殺成功? 場景映射 首先,我們舉一個生活中比較常見的例子:我們去銀行辦理業務,一般會選擇相關業務列印一個排號紙,然後就可以坐在小板凳上玩著手機,等待被小喇叭報 ...
  • fork/join作為一個併發框架在jdk7的時候就加入到了我們的java併發包java.util.concurrent中,並且在java 8 的lambda並行流中充當著底層框架的角色。這樣一個優秀的框架設計,我自己想瞭解一下它的底層代碼是如何實現的,所以我嘗試的去閱讀了JDK相關的源碼。下麵我打 ...
  • 所有的異常都有一個超類throwable; throwable有兩個子類:Exception和error(一般在重大錯誤,不能夠自行恢復); Exception有兩個子類:checked和runtime exception異常; checked:檢查時異常,就是程式代碼有的錯誤會有紅色波浪線的異常, ...
  • Description Mato同學最近正在研究一種矩陣,這種矩陣有n行n列第i行第j列的數為gcd(i,j)。 例如n=5時,矩陣如下: 1 1 1 1 1 1 2 1 2 1 1 1 3 1 1 1 2 1 4 1 1 1 1 1 5 Mato想知道這個矩陣的行列式的值,你能求出來嗎? Mato ...
  • 上次知識回顧:https://www.cnblogs.com/dotnetcrazy/p/9278573.html 代碼褲子:https://github.com/lotapp/BaseCode 線上編程:https://mybinder.org/v2/gh/lotapp/BaseCode/mast ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...