欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

從內(nèi)存模型中了解Java final的全部細(xì)節(jié)

 更新時(shí)間:2022年03月01日 16:26:14   作者:最愛(ài)吃魚(yú)罐頭  
關(guān)于final關(guān)鍵字,它也是我們一個(gè)經(jīng)常用的關(guān)鍵字,可以修飾在類上、或者修飾在變量、方法上,以此看來(lái)定義它的一些不可變性!像我們經(jīng)常使用的String類中,它便是final來(lái)修飾的類,并且它的字符數(shù)組也是被final所修飾的。但是一些final的一些細(xì)節(jié)你真的了解過(guò)嗎

茫茫人海千千萬(wàn)萬(wàn),感謝這一秒你看到這里。希望我的文章對(duì)你的有所幫助!

愿你在未來(lái)的日子,保持熱愛(ài),奔赴山海??!

從這篇文章開(kāi)始,帶你深入了解final的細(xì)節(jié)!

?? 從內(nèi)存模型中了解final

在上面,我們了解在單線程情況下的final,但對(duì)于多線程并發(fā)下的final,你有了解嗎?多線程并發(fā)的話,我們又必須知道一個(gè)內(nèi)存模型的概念:JMM。

?? JMM

JMM是定義了線程和主內(nèi)存之間的抽象關(guān)系:線程之間的共享變量存在主內(nèi)存(MainMemory)中,每個(gè)線程都有一個(gè)私有的本地內(nèi)存(LocalMemory)即共享變量副本,本地內(nèi)存中存儲(chǔ)了該線程以讀、寫(xiě)共享變量的副本。本地內(nèi)存是Java內(nèi)存模型的一個(gè)抽象概念,并不真實(shí)存在。它涵蓋了緩存、寫(xiě)緩沖區(qū)、寄存器等。

而在這一內(nèi)存模型下,計(jì)算機(jī)在執(zhí)行程序時(shí),為了提高性能,編譯器和處理器常常會(huì)對(duì)指令做重排序。那么問(wèn)題又來(lái)了,重排序是什么?

?? 重排序

其實(shí)對(duì)于我們程序來(lái)說(shuō),可以分為不同指令,每一個(gè)指令都會(huì)包含多個(gè)步驟,每個(gè)步驟可能使用不同的硬件。我們可以將每個(gè)指令拆分為五個(gè)階段:

想這樣如果是按順序串行執(zhí)行指令,那可能相對(duì)比較慢,因?yàn)樾枰却弦粭l指令完成后,才能等待下一步執(zhí)行:

而如果發(fā)生指令重排序呢,實(shí)際上雖然不能縮短單條指令的執(zhí)行時(shí)間,但是它變相地提高了指令的吞吐量,可以在一個(gè)時(shí)鐘周期內(nèi)同時(shí)運(yùn)行五條指令的不同階段。

我們來(lái)分析下代碼的執(zhí)行情況,并思考下:

a = b + c;
d = e - f ;

按原先的思路,會(huì)先加載b和c,再進(jìn)行b+c操作賦值給a,接下來(lái)就會(huì)加載e和f,最后就是進(jìn)行e-f操作賦值給d。

這里有什么優(yōu)化的空間呢?我們?cè)趫?zhí)行b+c操作賦值給a時(shí),可能需要等待b和c加載結(jié)束,才能再進(jìn)行一個(gè)求和操作,所以這里可能出現(xiàn)了一個(gè)停頓等待時(shí)間,依次后面的代碼也可能會(huì)出現(xiàn)停頓等待時(shí)間,這降低了計(jì)算機(jī)的執(zhí)行效率。

為了去減少這個(gè)停頓等待時(shí)間,我們可以先加載e和f,然后再去b+c操作賦值給a,這樣做對(duì)程序(串行)是沒(méi)有影響的,但卻減少了停頓等待時(shí)間。既然b+c操作賦值給a需要停頓等待時(shí)間,那還不如去做一些有意義的事情。

總結(jié):指令重排對(duì)于提高CPU處理性能十分必要。但是會(huì)因此引發(fā)一些指令的亂序。那么我們的final它對(duì)指令重排序有什么作用呢?接下來(lái)我們來(lái)看看吧!

?? final域重排序規(guī)則

對(duì)于JMM內(nèi)存模型來(lái)說(shuō),它對(duì)final域有以下兩種重排序規(guī)則:

  • 寫(xiě):在構(gòu)造函數(shù)內(nèi)對(duì)final域?qū)懭耄S后將構(gòu)造函數(shù)的引用賦值給一個(gè)引用變量,操作不能重排序。
  • 讀:初次讀一個(gè)包含final域的對(duì)象的引用和隨后初次寫(xiě)這個(gè)final域,不能重排序。

具體我們根據(jù)代碼演示一邊來(lái)講解吧:

代碼:

package com.ygt.test;

/**
 * 測(cè)試JMM內(nèi)存模型對(duì)final域重排序的規(guī)則
 */
public class JMMFinalTest {

    // 普通變量
    private int variable;
    // final變量
    private final int variable2;
    private static JMMFinalTest jmmFinalTest;

    // 構(gòu)造方法中,將普通變量和final變量進(jìn)行寫(xiě)的操作
    public JMMFinalTest(){
        variable = 1;  // 1. 寫(xiě)普通變量
        variable2 = 2; // 2. 寫(xiě)final變量
    }

    // 模仿一個(gè)寫(xiě)操作 --> 假設(shè)線程A進(jìn)行來(lái)寫(xiě)操作
    public static void write() {
        // new 當(dāng)前類對(duì)象 --> 并在構(gòu)造函數(shù)中完成賦值操作
        jmmFinalTest = new JMMFinalTest();
    }

    // 模仿一個(gè)讀操作 --> 假設(shè)線程B進(jìn)行來(lái)讀操作
    public static void read() {
        // 讀操作:
        JMMFinalTest test = jmmFinalTest; // 3. 讀對(duì)象的引用
        int localVariable = test.variable;
        int localVariable2 = test.variable2;
    }
}

寫(xiě)final域重排序規(guī)則

寫(xiě)final域重排序規(guī)則在構(gòu)造函數(shù)內(nèi)對(duì)final域?qū)懭?,隨后將構(gòu)造函數(shù)的引用賦值給一個(gè)引用變量,操作不能重排序。代表禁止對(duì)final域的初始化操作必須在構(gòu)造函數(shù)中,不能重排序到構(gòu)造函數(shù)之外,這個(gè)規(guī)則的實(shí)現(xiàn)主要包含了兩個(gè)方面:

  • JMM內(nèi)存模型禁止編譯器把final域的寫(xiě)重排序到構(gòu)造函數(shù)之外;
  • 編譯器會(huì)在final域?qū)懭牒蜆?gòu)造函數(shù)return返回之前,插入一個(gè)storestore內(nèi)存屏障。這個(gè)內(nèi)存屏障可以禁止處理器把final域的寫(xiě)重排序到構(gòu)造函數(shù)之外。

我們?cè)賮?lái)分析write方法,雖然只有一行代碼,但他實(shí)際上有三個(gè)步驟:

  • 在JVM的堆中申請(qǐng)一塊內(nèi)存空間
  • 對(duì)象進(jìn)行初始化操作
  • 將堆中的內(nèi)存空間的引用地址賦值給一個(gè)引用變量jmmFinalTest。

對(duì)于普通變量variable來(lái)說(shuō),它的初始化操作可以被重排序到構(gòu)造函數(shù)之外,即我們的步驟不是本來(lái)1-2-3嗎,現(xiàn)在可能造成1-3-2這樣初始化操作在構(gòu)造函數(shù)返回后了!

而對(duì)于final變量variable2來(lái)說(shuō),它的初始化操作一定在構(gòu)造函數(shù)之內(nèi),即1-2-3。

我們來(lái)看一個(gè)可能發(fā)生的圖:

對(duì)于變量的可見(jiàn)性來(lái)說(shuō),因?yàn)槠胀ㄗ兞縱ariable可能會(huì)發(fā)生重排序的一個(gè)現(xiàn)象,讀取的值可能會(huì)不一樣,可能是0或者是1。但是final變量variable2,它讀取的值一定是2了,因?yàn)橛袀€(gè)StoreStore內(nèi)存屏障來(lái)保證與下面的操作進(jìn)行重排序的操作。

由此可見(jiàn),寫(xiě)final域的重排序規(guī)則可以哪怕保證我們?cè)趯?duì)象引用為任意線程可見(jiàn)之前,對(duì)象的final域已經(jīng)被正確初始化過(guò)了,而普通域就不具有這個(gè)保障。

讀final域重排序規(guī)則

初次讀一個(gè)包含final域的對(duì)象的引用和隨后初次寫(xiě)這個(gè)final域,不能重排序。怎么實(shí)現(xiàn)呢?

它其實(shí)處理器會(huì)在讀final域操作的前面插入一個(gè)LoadLoad內(nèi)存屏障。

我們?cè)賮?lái)分析read方法,他實(shí)有三個(gè)步驟:

  • 初次讀引用變量jmmFinalTest;
  • 初次讀引用變量jmmFinalTest的普通域變量variable;
  • 初次讀引用變量jmmFinalTest的final域變量variable2;

我們以寫(xiě)操作正常排序的情況,對(duì)于讀情況可能發(fā)生圖解:

對(duì)于讀對(duì)象的普通域變量variable可能發(fā)生重排序的現(xiàn)象,被重排序到了讀對(duì)象引用的前面,此時(shí)就會(huì)出現(xiàn)線程B還未讀到對(duì)象引用就在讀取該對(duì)象的普通域變量,這顯然是錯(cuò)誤的操作。

而對(duì)于final域的讀操作通過(guò)LoadLoad內(nèi)存屏障保證在讀final域變量前已經(jīng)讀到了該對(duì)象的引用,從而就可以避免以上情況的發(fā)生。

由此可見(jiàn),讀final域的重排序規(guī)則可以確保我們?cè)谧x一個(gè)對(duì)象的final域之前,一定會(huì)先讀這個(gè)包含這個(gè)final域的對(duì)象的引用,而普通域就不具有這個(gè)保障。

?? final對(duì)象是引用類型

上面我已經(jīng)了解了final域?qū)ο笫腔緮?shù)據(jù)類型的一個(gè)重排序規(guī)則了,但是對(duì)象如果是引用類型呢?我們接著來(lái):

當(dāng)final域?qū)ο笫且粋€(gè)引用類型,寫(xiě)final域的重排序規(guī)則增加了如下的約束:

在構(gòu)造函數(shù)內(nèi)對(duì)一個(gè)final引用的對(duì)象的成員域的寫(xiě)入,與隨后在構(gòu)造函數(shù)外將被構(gòu)造對(duì)象的引用賦值給引用變量之間不能重排序。 聽(tīng)起來(lái)還是有點(diǎn)難懂是吧,沒(méi)事,代碼看看!

注意一點(diǎn):之前的寫(xiě)final域的重排序規(guī)則一樣存在,只是對(duì)引用類型對(duì)象增加了一條規(guī)則。

代碼:

package com.ygt.test;

/**
 * 測(cè)試final引用類型對(duì)象時(shí)的讀寫(xiě)情況
 */
public class ReferenceFinalTest {

    // 定義引用對(duì)象
    final Person person;
    private ReferenceFinalTest referenceFinalTest;

    // 在構(gòu)造函數(shù)中初始化,并進(jìn)行賦值操作
    public ReferenceFinalTest(){
        person = new Person(); // 1. 初始化
        person.setName("詹姆斯!"); // 2. 賦值
    }

    // 線程A進(jìn)來(lái)進(jìn)行寫(xiě)操作,實(shí)現(xiàn)將referenceFinalTest初始化
    public void write(){
        referenceFinalTest = new ReferenceFinalTest(); // 3. 初始化構(gòu)造函數(shù)
    }

    // 線程B進(jìn)來(lái)進(jìn)行寫(xiě)操作,實(shí)現(xiàn)person重新賦值操作。
    public void write2(){
       person.setName("戴維斯"); // 4. 重新賦值操作
    }

    // 線程C進(jìn)來(lái)進(jìn)行讀操作,讀取當(dāng)前person的值
    public void read(){
        if(referenceFinalTest != null) { // 5. 讀取引用對(duì)象
            String name = person.getName(); // 6. 讀取person對(duì)象的值
        }
    }
}

class Person{
    private String name;
    private int age;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

首先,我們先畫(huà)個(gè)可能發(fā)生情況的圖解:

我們線程的執(zhí)行順序:A ——> B ——> C

接著我們對(duì)讀寫(xiě)操作方法進(jìn)行詳解:

寫(xiě)final域重排序規(guī)則

從之前我們就知道,我們final域的寫(xiě)禁止重排序到構(gòu)造方法外,因此1和3是不能發(fā)生重排序現(xiàn)象滴。

而對(duì)于我們新增的約束來(lái)說(shuō),在構(gòu)造函數(shù)內(nèi)對(duì)一個(gè)final引用的對(duì)象的成員域的寫(xiě)入,與隨后在構(gòu)造函數(shù)外將被構(gòu)造對(duì)象的引用賦值給引用變量之間不能重排序。即final域的引用對(duì)象的成員屬性寫(xiě)入setName("詹姆斯")是不可以與隨后將這個(gè)被構(gòu)造出來(lái)的對(duì)象賦給引用變量jmmFinalTest重排序,因此2和3不能重排序。

所以我們的步驟是1-2-3。

讀final域重排序規(guī)則

對(duì)于多線程情況下,JMM內(nèi)存模型至少可以確保線程C在讀對(duì)象person的成員屬性時(shí),先讀取到了引用對(duì)象person了,可以讀取到線程A對(duì)final域引用對(duì)象person的成員屬性的寫(xiě)入。

可能此時(shí)線程B對(duì)于person的成員屬性的寫(xiě)入暫時(shí)看不到,保證不了線程B的寫(xiě)入對(duì)線程C的可見(jiàn)性,因?yàn)榭赡芫€程B與線程C存在了線程搶占的競(jìng)爭(zhēng)問(wèn)題,此時(shí)的結(jié)果可能不同!

當(dāng)然,如果想要保存可見(jiàn),我們可以使用Volatile或者同步鎖。

?? 小結(jié)

我們可以根據(jù)數(shù)據(jù)類型分類:

基本數(shù)據(jù)類型:

  • 寫(xiě):在構(gòu)造函數(shù)內(nèi)對(duì)final域?qū)懭?,隨后將構(gòu)造函數(shù)的引用賦值給一個(gè)引用變量,操作不能重排序。即禁止final域?qū)懼嘏判虻綐?gòu)造方法之外。
  • 讀:初次讀一個(gè)包含final域的對(duì)象的引用和隨后初次寫(xiě)這個(gè)final域,不能重排序。

引用數(shù)據(jù)類型:

在基本數(shù)據(jù)類型上額外增加約束:

禁止在構(gòu)造函數(shù)對(duì)一個(gè)final修飾的對(duì)象的成員域?qū)傩缘膶?xiě)入與隨后將這個(gè)被構(gòu)造的對(duì)象的引用賦值給引用變量進(jìn)行重排序。

??總結(jié)

相信各位看官都對(duì)final這一個(gè)關(guān)鍵字有了一定了解吧,其實(shí)額外擴(kuò)展自己的知識(shí)面也是相當(dāng)有必要滴,不然別人追問(wèn)你的時(shí)候,你會(huì)啞口無(wú)言,而一旦你自己每天都深入剖析知識(shí)點(diǎn)后,你在今后的對(duì)答中都會(huì)滔滔不絕,綻放光芒的?。。?duì)吧,我們還有一把東西等著我們探索和摸索中!接下來(lái)就是潛心學(xué)習(xí)一段時(shí)間,不浮躁,不氣餒!

讓我們也一起加油吧!本人不才,如有什么缺漏、錯(cuò)誤的地方,也歡迎各位人才大佬評(píng)論中批評(píng)指正!當(dāng)然如果這篇文章確定對(duì)你有點(diǎn)小小幫助的話,也請(qǐng)親切可愛(ài)的人才大佬們給個(gè)點(diǎn)贊、收藏下吧,一鍵三連,非常感謝!

學(xué)到這里,今天的世界打烊了,晚安!雖然這篇文章完結(jié)了,但是我還在,永不完結(jié)。我會(huì)努力保持寫(xiě)文章。來(lái)日方長(zhǎng),何懼車遙馬慢!

感謝各位看到這里!愿你韶華不負(fù),青春無(wú)悔!

到此這篇關(guān)于從內(nèi)存模型中了解Java final的全部細(xì)節(jié)的文章就介紹到這了,更多相關(guān)Java final內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 基于OpenCv與JVM實(shí)現(xiàn)加載保存圖像功能(JAVA?圖像處理)

    基于OpenCv與JVM實(shí)現(xiàn)加載保存圖像功能(JAVA?圖像處理)

    openCv有一個(gè)名imread的簡(jiǎn)單函數(shù),用于從文件中讀取圖像,本文給大家介紹JAVA?圖像處理基于OpenCv與JVM實(shí)現(xiàn)加載保存圖像功能,感興趣的朋友一起看看吧
    2022-01-01
  • 詳解mybatis foreach collection示例

    詳解mybatis foreach collection示例

    這篇文章主要介紹了詳解mybatis foreach collection的相關(guān)資料,需要的朋友可以參考下
    2017-10-10
  • SpringBoot HttpMessageConverter消息轉(zhuǎn)換器的使用詳解

    SpringBoot HttpMessageConverter消息轉(zhuǎn)換器的使用詳解

    在整個(gè)數(shù)據(jù)流轉(zhuǎn)過(guò)程中,前端的請(qǐng)求報(bào)文轉(zhuǎn)化為Java對(duì)象,Java對(duì)象轉(zhuǎn)化為響應(yīng)報(bào)文,這里就用到了消息轉(zhuǎn)換器HttpMessageConverter
    2022-06-06
  • Java深入分析講解反射機(jī)制

    Java深入分析講解反射機(jī)制

    反射是框架的靈魂,Java框架底層都是用反射機(jī)制+xml配置等來(lái)實(shí)現(xiàn)的,本文將通過(guò)示例詳細(xì)講解Java中的反射機(jī)制,感興趣的小伙伴可以跟隨小編學(xué)習(xí)一下
    2022-06-06
  • java通過(guò)信號(hào)量實(shí)現(xiàn)限流的示例

    java通過(guò)信號(hào)量實(shí)現(xiàn)限流的示例

    本文主要介紹了java通過(guò)信號(hào)量實(shí)現(xiàn)限流的示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-06-06
  • 詳解DES加密算法的原理與Java實(shí)現(xiàn)

    詳解DES加密算法的原理與Java實(shí)現(xiàn)

    DES 加密,是對(duì)稱加密。對(duì)稱加密,顧名思義,加密和解密的運(yùn)算全都是使用的同樣的秘鑰。這篇文章主要為大家講講DES加密算法的原理與Java實(shí)現(xiàn),需要的可以參考一下
    2022-10-10
  • 一文了解為什么Java中只有值傳遞

    一文了解為什么Java中只有值傳遞

    Java?傳參是值傳遞還是引用傳遞?這個(gè)問(wèn)題很基礎(chǔ),但是許多人都有點(diǎn)懵。本文就來(lái)通過(guò)一些示例帶大家詳細(xì)了解一下,需要的可以參考一下
    2022-07-07
  • Java稀疏數(shù)組詳細(xì)圖文教程

    Java稀疏數(shù)組詳細(xì)圖文教程

    當(dāng)一個(gè)數(shù)組中的大部分元素為相同的值,可使用稀疏數(shù)組來(lái)保存該數(shù)組,可以將稀疏數(shù)組看做是普通數(shù)組的壓縮,這篇文章主要給大家介紹了關(guān)于Java稀疏數(shù)組的相關(guān)資料,需要的朋友可以參考下
    2023-09-09
  • Java Calendar類的時(shí)間操作

    Java Calendar類的時(shí)間操作

    這篇文章主要為大家詳細(xì)介紹了Java Calendar類的時(shí)間操作,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-10-10
  • ehcache模糊批量移除緩存的方法

    ehcache模糊批量移除緩存的方法

    本篇文章主要介紹了ehcache模糊批量移除緩存的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-02-02

最新評(píng)論