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

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

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

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

愿你在未來的日子,保持熱愛,奔赴山海!!

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

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

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

?? JMM

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

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

?? 重排序

其實對于我們程序來說,可以分為不同指令,每一個指令都會包含多個步驟,每個步驟可能使用不同的硬件。我們可以將每個指令拆分為五個階段:

想這樣如果是按順序串行執(zhí)行指令,那可能相對比較慢,因為需要等待上一條指令完成后,才能等待下一步執(zhí)行:

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

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

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

按原先的思路,會先加載b和c,再進行b+c操作賦值給a,接下來就會加載e和f,最后就是進行e-f操作賦值給d。

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

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

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

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

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

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

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

代碼:

package com.ygt.test;

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

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

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

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

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

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

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

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

我們再來分析write方法,雖然只有一行代碼,但他實際上有三個步驟:

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

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

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

我們來看一個可能發(fā)生的圖:

對于變量的可見性來說,因為普通變量variable可能會發(fā)生重排序的一個現(xiàn)象,讀取的值可能會不一樣,可能是0或者是1。但是final變量variable2,它讀取的值一定是2了,因為有個StoreStore內(nèi)存屏障來保證與下面的操作進行重排序的操作。

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

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

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

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

我們再來分析read方法,他實有三個步驟:

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

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

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

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

由此可見,讀final域的重排序規(guī)則可以確保我們在讀一個對象的final域之前,一定會先讀這個包含這個final域的對象的引用,而普通域就不具有這個保障。

?? final對象是引用類型

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

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

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

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

代碼:

package com.ygt.test;

/**
 * 測試final引用類型對象時的讀寫情況
 */
public class ReferenceFinalTest {

    // 定義引用對象
    final Person person;
    private ReferenceFinalTest referenceFinalTest;

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

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

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

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

class Person{
    private String name;
    private int age;

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

    public String getName() {
        return name;
    }
}

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

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

接著我們對讀寫操作方法進行詳解:

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

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

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

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

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

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

可能此時線程B對于person的成員屬性的寫入暫時看不到,保證不了線程B的寫入對線程C的可見性,因為可能線程B與線程C存在了線程搶占的競爭問題,此時的結(jié)果可能不同!

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

?? 小結(jié)

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

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

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

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

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

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

??總結(jié)

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

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

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

感謝各位看到這里!愿你韶華不負,青春無悔!

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

相關(guān)文章

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

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

    openCv有一個名imread的簡單函數(shù),用于從文件中讀取圖像,本文給大家介紹JAVA?圖像處理基于OpenCv與JVM實現(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)換器的使用詳解

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

    Java深入分析講解反射機制

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

    java通過信號量實現(xiàn)限流的示例

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

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

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

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

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

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

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

    Java Calendar類的時間操作

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

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

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

最新評論