亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Java內(nèi)存模型final的內(nèi)存語義

 更新時間:2021年11月04日 10:19:14   作者:李子捌  
這篇文章主要介紹了Java內(nèi)存模型final的內(nèi)存語義,上篇介紹volatile的內(nèi)存語義,本文講述的是final的內(nèi)存語義,相比之下,final域的讀和寫更像是普通變量的訪問。下面我們一起來看看文章學(xué)校內(nèi)容吧,需要的朋友可以參考一下

上篇并發(fā)編程之Java內(nèi)存模型volatile的內(nèi)存語義介紹了volatile的內(nèi)存語義,本文講述的是final的內(nèi)存語義,相比之下,final域的讀和寫更像是普通變量的訪問。

1、final域的重排序規(guī)則final

對于final域編譯器和處理器遵循兩個重排序規(guī)則

  • 在構(gòu)造函數(shù)內(nèi)對一個final域的寫入,與隨后把這個對象的引用賦值給另一個引用變量,這兩個操作之間不能重排序
  • 初次讀一個包含final域的對象的引用,與隨后初次讀這個final域,這兩個操作之間不能重排序。

用代碼來說明上面兩種重排序規(guī)則:

package com.lizba.p1;

/**
 * <p>
 *
 * </p>
 *
 * @Author: Liziba
 * @Date: 2021/6/11 20:37
 */
public class FinalExample {

    /** 普通變量 */
    int i;
    /** final變量 */
    final int j;
    /** 對象引用 */
    static FinalExample obj;

    /**
     * 構(gòu)造函數(shù)
     */
    public FinalExample() {
        // 寫普通域
        this.i = 1;
        // 寫final域
        this.j = 2;
    }

    /**
     * 線程A執(zhí)行writer寫方法
     *
     */
    public static void writer() {
        obj = new FinalExample();
    }

    /**
     * 線程B執(zhí)行reader讀方法
     *
     */
    public static void reader() {
        // 讀對象的引用
        FinalExample finalExample = obj;
        // 讀普通域
        int a = finalExample.i;
        // 讀final域
        int b = finalExample.j;
    }
}

假設(shè)線程A執(zhí)行writer()方法,線程B執(zhí)行reader()方法。下面來通過這兩個線程的交互來說明這兩個規(guī)則。

2、寫final域的重排序規(guī)則

寫final域的重排序禁止吧final域的寫重排序到構(gòu)造函數(shù)之外。通過如下方式來實現(xiàn):

  • JMM禁止編譯器把final域的寫重排序到構(gòu)造函數(shù)之外
  • 編譯器會在final域的寫之后,構(gòu)造函數(shù)return之前,插入一個StoreStore屏障。這個屏障禁止處理器把final域的寫重排序到構(gòu)造函數(shù)之外。

現(xiàn)在開始分析writer()方法:

 /**
   * 線程A執(zhí)行writer寫方法
   *
   */
public static void writer() {
    obj = new FinalExample();
}

  • 構(gòu)造一個FinalExample類型的對象
  • 將對象的引用賦值給變量obj

首先假設(shè)線程B讀對象引用與讀對象的成員域之間沒有重排序,則下圖是其一種執(zhí)行可能

 線程執(zhí)行時序圖:

3、讀final與的重排序規(guī)則

讀final域的重排序規(guī)則是,在一個線程中,初次讀對象引用與初次讀該對象包含的final域,JMM禁止處理器重排序這兩個操作(注意是處理器)。編譯器會在讀final域操作的前面插入一個LoadLoad屏障。

解釋:初次讀對象引用與初次讀該對象包含的final域,這兩個操作之間存在間接依賴關(guān)系。

  • 編譯器遵守間接依賴關(guān)系,編譯器不會重排序這兩個操作
  • 大多數(shù)處理器也遵守間接依賴,不會重排序這兩個操作。但是少部分處理器允許對存在間接依賴關(guān)系的操作做重排序(比如alpha處理器),這個規(guī)則就是專門針對這種處理器的。

分析reader()方法:

  /**
    * 線程B執(zhí)行reader讀方法
    *
    */
public static void reader() {
    // 讀對象的引用
    FinalExample finalExample = obj;
    // 讀普通域
    int a = finalExample.i;
    // 讀final域
    int b = finalExample.j;
}

  • 初次讀引用變量obj
  • 初次讀引用變量obj指向?qū)ο蟮钠胀ㄓ騤
  • 初次讀引用變量obj指向?qū)ο蟮膄inal域i

假設(shè)B線程所處的處理器不遵守間接依賴關(guān)系,且A線程執(zhí)行過程中沒有發(fā)生任何重排序,此時存在如下的執(zhí)行時序:

線程執(zhí)行時序圖:

上圖B線程中讀對象的普通域被重排序到處理器讀取對象引用之前, 此時普通域i還沒有被線程A寫入,因此這是一個錯誤的讀取操作。但是final域的讀取會被重排序規(guī)則把讀final域的操作“限定”在讀該final域所屬對象的引用讀取之后,此時final域已經(jīng)被正確的初始化了,這是一個正確的讀取操作。

總結(jié):

讀final域的重排序規(guī)則可以確保,在讀一個對象的final域之前,一定會先讀包含這個final域的對象的引用。

4、final域為引用類型

上面講述了基礎(chǔ)數(shù)據(jù)類型,如果final域修飾的引用類型又該如何?

package com.lizba.p1;

/**
 * <p>
 *      final 修飾引用類型變量
 * </p>
 *
 * @Author: Liziba
 * @Date: 2021/6/11 21:52
 */
public class FinalReferenceExample {

    /** final是引用類型 */
    final int[] intArray;
    static FinalReferenceExample obj;
    
    /**
     * 構(gòu)造函數(shù)
     */
    public FinalReferenceExample() {
        this.intArray = new int[1];  // 1
        intArray[0] = 1;             // 2
    }

    /**
     * 寫線程A執(zhí)行
     */
    public static void writer1() {
        obj = new FinalReferenceExample();      // 3
    }

    /**
     * 寫線程B執(zhí)行
     */
    public static void writer2() {
        obj.intArray[0] = 2;                    // 4
    }

    /**
     * 讀線程C執(zhí)行
     */
    public static void reader() {
        if (obj != null) {                      // 5
            int temp = obj.intArray[0];         // 6
        }
    }
}

如上final域為一個int類型的數(shù)組的引用變量。對應(yīng)引用類型,寫final域的重排序?qū)幾g器和處理器增加了如下約束:

  • 在構(gòu)造函數(shù)內(nèi)對一個final引用的對象的成員域的寫入,與隨后在構(gòu)造函數(shù)外把這個被構(gòu)造對象的引用賦值給另一個引用變量,這兩個操作不能重排序。

對于上述程序,假設(shè)A執(zhí)行writer1()方法,執(zhí)行完后線程B執(zhí)行writer2()方法,執(zhí)行完后線程C執(zhí)行reader()方法。則存在如下線

程執(zhí)行時序:引用型final的執(zhí)行時序圖

JMM對于上述代碼,可以確保讀線程C至少能看到寫線程A在構(gòu)造函數(shù)中對final引用對象的成員域的寫入。即寫線程C至少能看到數(shù)組下標(biāo)0的值為1。但是寫線程B對數(shù)組元素的寫入,讀線程C可能看得到可能看不到。JMM不能保證線程B的寫入對讀線程C可見。因為寫線程B和讀線程C之間存在數(shù)據(jù)競爭,此時的執(zhí)行結(jié)果不可預(yù)知。

此時如果想確保讀線程C看到寫線程B對數(shù)組元素的寫入,可以結(jié)合同步原語(volatile或者lock)來實現(xiàn)。

5、為什么final引用不能從構(gòu)造函數(shù)內(nèi)“逸出”

本文一直在說寫final域的重排序規(guī)則可以確保:在引用變量為任意線程可見之前,該引用變量指向的對象的final域已經(jīng)在構(gòu)造函數(shù)中被正確初始化了。那究竟是如何實現(xiàn)的呢?

其實這需要另一個條件:在構(gòu)造函數(shù)內(nèi)部,不能讓這個被構(gòu)造對象的引用被其它線程所見。也就是對象引用不能在構(gòu)造函數(shù)中“逸出”。

示例代碼:

package com.lizba.p1;

/**
 * <p>
 *   final引用逸出demo
 * </p>
 *
 * @Author: Liziba
 * @Date: 2021/6/11 22:33
 */
public class FinalReferenceEscapeExample {

    final int i;
    static FinalReferenceEscapeExample obj;

    public FinalReferenceEscapeExample() {
        i = 1;                            // 1、寫final域
        obj = this;              // 2、this引用在此處"逸出"
    }

    public static void writer() {
        new FinalReferenceEscapeExample();
    }

    public static void reader() {
        if (obj != null) {                 // 3
            int temp = obj.i;              // 4
        }
    }
}

假設(shè)線程A執(zhí)行writer()方法,線程B執(zhí)行reader()方法。這里操作2導(dǎo)致對象還未完成構(gòu)造前就對線程B可見了。因為1和2允許重排序,所以線程B可能無法看到final域被正確初始化后的值。實際執(zhí)行的時序圖可能如下所示:

多線程執(zhí)行時序圖:

總結(jié):

在構(gòu)造函數(shù)返回之前,被構(gòu)造對象的引用不能為其他線程可見,因為此時的final域可能還沒被初始化。而在構(gòu)造函數(shù)返回后,任意線程都將保證能看到final域正確初始化之后的值。

6、final語義在處理器中的實現(xiàn)

舉例X86處理器中final語義的具體實現(xiàn)。

在編譯器中會存在如下的處理:

  • 寫final域的重排序規(guī)則會要求編譯器在final域的寫之后,構(gòu)造函數(shù)return之前插入一個StoreStore屏障
  • 讀final域的重排序規(guī)則要求編譯器在讀final域的操作前插入一個LoadLoad屏障

但是,由于X86處理器不會對寫-寫操作做重排序,所以在X86處理器中,寫final域需要的StoreStore屏障會被省略。同樣,由于X86處理器不會對存在間接依賴關(guān)系的操作做重排序,所以在X86處理器中,讀final域需要的LoadLoad屏障也會被省略掉。因此,在X86處理器中,final域的讀/寫不會插入任何內(nèi)存屏障。

7、JSR-133為什么要增強(qiáng)final的語義

在舊的Java內(nèi)存模型中,一個最嚴(yán)重的缺陷就是現(xiàn)場可能看到final域的值會改變。比如一個線程讀取一個被final域的值為0(未初始化之前的默認(rèn)值),過一段時間再讀取初始化后的final域的值,卻發(fā)現(xiàn)變?yōu)榱?。因此為了修復(fù)此漏洞,JSR-133增強(qiáng)了final語義。

總結(jié):

通過為final增加寫和讀重排序規(guī)則,可以為Java程序員提供初始化安全保障:只要對象正確構(gòu)造(被構(gòu)造對象額引用在構(gòu)造函數(shù)中沒有“逸出”),那么不需要使用同步原語(volatile和lock的使用)就可以保障任意線程都能看到這個final域在構(gòu)造函數(shù)中被初始化之后的值。

到此這篇關(guān)于Java內(nèi)存模型final的內(nèi)存語義的文章就介紹到這了,更多相關(guān)Java內(nèi)存模型final的內(nèi)存語義內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java壓縮文件ZIP實例代碼

    Java壓縮文件ZIP實例代碼

    這篇文章主要介紹了Java壓縮文件ZIP實例代碼,有需要的朋友可以參考一下
    2013-12-12
  • java中final修飾符的使用方法

    java中final修飾符的使用方法

    這篇文章主要為大家詳細(xì)介紹了java中final修飾符的使用方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-07-07
  • javaweb設(shè)計中filter粗粒度權(quán)限控制代碼示例

    javaweb設(shè)計中filter粗粒度權(quán)限控制代碼示例

    這篇文章主要介紹了javaweb設(shè)計中filter粗粒度權(quán)限控制代碼示例,小編覺得還是挺不錯的,需要的朋友可以參考。
    2017-10-10
  • Java構(gòu)建JDBC應(yīng)用程序的實例操作

    Java構(gòu)建JDBC應(yīng)用程序的實例操作

    在本篇文章里小編給大家整理了一篇關(guān)于Java構(gòu)建JDBC應(yīng)用程序的實例操作,有興趣的朋友們可以學(xué)習(xí)參考下。
    2021-03-03
  • Java讀取Map的兩種方法與對比

    Java讀取Map的兩種方法與對比

    相信大家都知道在Java中Map的使用非常頻繁,我們經(jīng)常會需要對Map進(jìn)行遍歷和讀取,那么下面這篇文章將展示兩種遍歷的方法以及簡要分析。有需要的可以參考借鑒,下面來一起看看吧。
    2016-11-11
  • java中List常用的4種stream()方法解析

    java中List常用的4種stream()方法解析

    Java中的List接口從Java 8開始新增了stream()方法,用于創(chuàng)建一個Stream流對象,這篇文章主要給大家介紹了關(guān)于java中List常用的4種stream()方法的相關(guān)資料,需要的朋友可以參考下
    2024-02-02
  • 在idea中將java項目中的單個類打包成jar包操作

    在idea中將java項目中的單個類打包成jar包操作

    這篇文章主要介紹了在idea中將java項目中的單個類打包成jar包操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-08-08
  • 一文教會你如何從0到1搭建一個SpringBoot項目

    一文教會你如何從0到1搭建一個SpringBoot項目

    今天剛好學(xué)習(xí)到SpringBoot,就順便記錄一下吧,下面這篇文章主要給大家介紹了關(guān)于如何從0到1搭建一個SpringBoot項目的相關(guān)資料,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下
    2024-01-01
  • Java的ArrayList擴(kuò)容源碼解析

    Java的ArrayList擴(kuò)容源碼解析

    這篇文章主要介紹了Java的ArrayList擴(kuò)容源碼解析,通過動態(tài)擴(kuò)容,ArrayList能夠在添加元素時保持高效的性能,擴(kuò)容操作是有一定開銷的,但由于擴(kuò)容的時間復(fù)雜度為O(n),其中n是當(dāng)前元素個數(shù),所以平均情況下,每次添加元素的時間復(fù)雜度仍然是O(1),需要的朋友可以參考下
    2024-01-01
  • SpringBoot @ExceptionHandler與@ControllerAdvice異常處理詳解

    SpringBoot @ExceptionHandler與@ControllerAdvice異常處理詳解

    在Spring Boot應(yīng)用的開發(fā)中,不管是對底層數(shù)據(jù)庫操作,對業(yè)務(wù)層操作,還是對控制層操作,都會不可避免的遇到各種可預(yù)知的,不可預(yù)知的異常需要處理,如果每個處理過程都單獨處理異常,那么系統(tǒng)的代碼耦合度會很高,工作量大且不好統(tǒng)一,以后維護(hù)的工作量也很大
    2022-10-10

最新評論