對(duì)于 final 域,編譯器和處理器要遵守兩個(gè)重排序規(guī)則:
舉個(gè)例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20  |     int i;// 普通變量    final int j;// final 變量    static FinalExample obj;    public FinalExample() {        i = 1;// 寫(xiě)普通域        j = 2;// 寫(xiě) final 域    }    public static void writer() {// 寫(xiě)線(xiàn)程 A 執(zhí)行        obj = new FinalExample();    }    public static void reader() {// 讀線(xiàn)程 B 執(zhí)行        FinalExample object = obj;        int a = object.i;        int b = object.j;    }} | 
這里假設(shè)一個(gè)線(xiàn)程 A 執(zhí)行 writer ()方法,隨后另一個(gè)線(xiàn)程 B 執(zhí)行 reader ()方法。
在寫(xiě) final 域的時(shí)候有兩個(gè)規(guī)則:
分析上面的代碼。
write 方法,只包含一行 obj = new FinalExample();,但是包含兩個(gè)步驟:
假設(shè)線(xiàn)程 B 當(dāng)中讀 obj 與讀成員域之間沒(méi)有重排序。那么執(zhí)行時(shí)序可能如下:

寫(xiě) final 域的重排序規(guī)則可以確保:在對(duì)象引用為任意線(xiàn)程可見(jiàn)之前,對(duì)象的 final 域已經(jīng)被正確初始化過(guò)了,而普通域不具有這個(gè)保障。
讀 final 域的重排序規(guī)則如下:
reader() 方法包含三個(gè)操作:
現(xiàn)在我們假設(shè)寫(xiě)線(xiàn)程 A 沒(méi)有發(fā)生任何重排序,那么執(zhí)行時(shí)序可能是:

上面的圖可以看到對(duì)普通變量 i 的讀取重排序到了讀對(duì)象引用之前,在讀普通域時(shí)候,該域還沒(méi)被寫(xiě)線(xiàn)程 A 寫(xiě)入,這是一個(gè)錯(cuò)誤的讀取操作。而讀 final 域已經(jīng)被 A 線(xiàn)程初始化了,這個(gè)讀取操作是正確的。
讀 final 域的重排序規(guī)則可以確保:在讀一個(gè)對(duì)象的 final 域之前,一定會(huì)先讀包含 這個(gè) final 域的對(duì)象的引用。在這個(gè)示例程序中,如果該引用不為 null,那么引用 對(duì)象的 final 域一定已經(jīng)被 A 線(xiàn)程初始化過(guò)了。
如果 final 域是引用類(lèi)型,寫(xiě) final 域的重排序規(guī)則對(duì)編譯器和處理器增加了如下約束:
如下代碼例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19  | public class FinalReferenceExample {    final int[] intArray;    static FinalReferenceExample obj;    public FinalReferenceExample() {        intArray = new int[1];// 1        intArray[0] = 1;// 2    }    public static void writerOne() {// A線(xiàn)程執(zhí)行        obj = new FinalReferenceExample(); // 3    }    public static void reader() {// 寫(xiě)線(xiàn)程 B 執(zhí)行        if (obj != null) { // 4            int temp1 = obj.intArray[0]; // 5        }    }} | 
假設(shè)首先線(xiàn)程 A 執(zhí)行 writerOne()方法,執(zhí)行完后線(xiàn)程 B 執(zhí)行reader 方法,JMM 可以確保讀線(xiàn)程 B 至少能看到寫(xiě)線(xiàn)程 A 在構(gòu)造函數(shù)中對(duì) final 引用對(duì)象的成員域的寫(xiě)入。
代碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19  | public class FinalReferenceEscapeExample {    final int i;    static FinalReferenceEscapeExample obj;    public FinalReferenceEscapeExample() {        i = 1;// 1        obj = this;// 2 避免怎么做!!!    }    public static void writer() {        new FinalReferenceEscapeExample();    }    public static void reader() {        if (obj != null) {// 3            int temp = obj.i; // 4        }    }} | 
假設(shè)一個(gè)線(xiàn)程 A 執(zhí)行 writer()方法,另一個(gè)線(xiàn)程 B 執(zhí)行 reader()方法。
這里的操作 2 使得對(duì)象還未完成構(gòu)造前就為線(xiàn)程 B 可見(jiàn)。即使這里的操作 2 是構(gòu)造函數(shù)的最后 一步,且即使在程序中操作 2 排在操作 1 后面,執(zhí)行 read()方法的線(xiàn)程仍然可能無(wú) 法看到 final 域被初始化后的值,因?yàn)檫@里的操作 1 和操作 2 之間可能被重排序。
在構(gòu)造函數(shù)返回前,被構(gòu)造對(duì)象的引用不能為其他線(xiàn)程可 見(jiàn),因?yàn)榇藭r(shí)的 final 域可能還沒(méi)有被初始化。在構(gòu)造函數(shù)返回后,任意線(xiàn)程都將 保證能看到 final 域正確初始化之后的值。
全能程序員交流QQ群290551701,群內(nèi)程序員都是來(lái)自,百度、阿里、京東、小米、去哪兒、餓了嗎、藍(lán)港等高級(jí)程序員 ,擁有豐富的經(jīng)驗(yàn)。加入我們,直線(xiàn)溝通技術(shù)大牛,最佳的學(xué)習(xí)環(huán)境,了解業(yè)內(nèi)的一手的資訊。如果你想結(jié)實(shí)大牛,那 就加入進(jìn)來(lái),讓大牛帶你超神!
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注