首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Java 內存模型深度解析:final 域的重排序規則與實現原理

     編輯:Java 綜合教學

Java 內存模型深度解析:final 域的重排序規則與實現原理

Java 內存模型(JMM)對 final 域有特殊的重排序規則,提供初始化安全保證。本文通過代碼示例、時序圖解析寫/讀 final 域的兩大重排序規則,並深入探討引用類型 final 域、構造函數逸出問題及 x86 處理器實現細節。

核心規則概覽

規則約束對象實現手段保證效果
寫 final 域重排序規則編譯器、處理器禁止 final 域寫重排到構造函數外 + 構造函數返回前插入 StoreStore 屏障對象引用可見時,final 域已正確初始化
讀 final 域重排序規則處理器禁止初次讀對象引用與讀 final 域重排序 + 讀 final 域前插入 LoadLoad 屏障讀 final 域前,必先讀對象引用,保證看到初始化值

示例程序分析

public class FinalExample {
    int i;           // 普通域
    final int j;     // final 域
    static FinalExample obj;

    public FinalExample() {
        i = 1;       // 寫普通域
        j = 2;       // 寫 final 域
    }

    public static void writer() { obj = new FinalExample(); }  // 線程 A
    public static void reader() {                              // 線程 B
        FinalExample object = obj;
        int a = object.i;  // 可能讀到 0(重排序導致)
        int b = object.j;  // 一定讀到 2(受 final 規則保護)
    }
}

寫 final 域規則效果

普通域 i 的寫操作可能被重排序到構造函數外,導致線程 B 讀到初始值 0。而 final 域 j 的寫被「限定」在構造函數內,配合 StoreStore 屏障,確保線程 B 看到對象引用時 j 已初始化為 2。

讀 final 域規則效果

處理器可能將讀普通域操作重排到讀對象引用之前,導致讀取未初始化的值。讀 final 域規則通過 LoadLoad 屏障強制讀 final 域操作在讀對象引用之後,此時 final 域已被寫線程初始化。

引用類型 final 域的特殊約束

public class FinalReferenceExample {
    final int[] intArray;  // final 引用類型
    static FinalReferenceExample obj;

    public FinalReferenceExample() {
        intArray = new int[1];  // 1: 寫 final 引用
        intArray[0] = 1;        // 2: 寫引用對象的成員域
    }

    public static void writerOne() { obj = new FinalReferenceExample(); }  // 3: 寫對象引用
    public static void writerTwo() { obj.intArray[0] = 2; }                // 4: 另一線程修改數組元素
    public static void reader() {                                          // 線程 C
        if (obj != null) {
            int temp1 = obj.intArray[0];  // 至少看到 1,不保證看到 2
        }
    }
}

JMM 確保:1 不能與 3 重排序,2 也不能與 3 重排序。讀線程 C 至少能看到構造函數中對 final 引用對象成員域的寫入(即數組下標 0 為 1)。但寫線程 B 對數組元素的修改(操作 4)對 C 不保證可見,存在數據競爭,需額外同步手段。

為什錝 final 引用不能從構造函數「逸出」

public class FinalReferenceEscapeExample {
    final int i;
    static FinalReferenceEscapeExample obj;

    public FinalReferenceEscapeExample() {
        i = 1;          // 1: 寫 final 域
        obj = this;     // 2: this 引用逸出(構造未完成就對其他線程可見)
    }
}

即使操作 2 在代碼順序上在操作 1 之後,編譯器/處理器仍可能重排序。線程 B 可能看到 obj != null 但 obj.i 仍為 0。結論:構造函數返回前,被構造對象引用不可為其他線程可見。

x86 處理器上的實現細節

  • x86 不會對寫-寫重排序 → StoreStore 屏障省略
  • x86 不會對存在間接依賴的操作重排序 → LoadLoad 屏障省略
  • 結果:x86 上 final 域讀/寫不插入任何內存屏障,性能開銷極低

JSR-133 增強 final 語義的歷史動機

舊內存模型中,線程可能先看到 final 域默認值(如 0),後看到初始化值(如 1),甚至 String 值可能改變。JSR-133 通過最終域重排序規則,提供初始化安全保證:正確構造的對象(無 this 逸出)無需同步,任意線程均可見 final 域初始化後的值。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved