激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術(shù)|正則表達(dá)式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務(wù)器之家 - 編程語言 - Java教程 - Java內(nèi)存模型final的內(nèi)存語義

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

2022-03-11 00:43李子捌 Java教程

這篇文章主要介紹了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

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

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

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

package com.lizba.p1;

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

  /** 普通變量 */
  int i;
  /** final變量 */
  final int j;
  /** 對(duì)象引用 */
  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() {
      // 讀對(duì)象的引用
      FinalExample finalExample = obj;
      // 讀普通域
      int a = finalExample.i;
      // 讀final域
      int b = finalExample.j;
  }
}

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

 

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

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

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

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

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

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

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

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

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

 

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

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

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

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

分析reader()方法:

/**
  * 線程B執(zhí)行reader讀方法
  *
  */
public static void reader() {
  // 讀對(duì)象的引用
  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ā)生任何重排序,此時(shí)存在如下的執(zhí)行時(shí)序:

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

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

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

總結(jié):

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

 

4、final域?yàn)橐妙愋?/h2>

上面講述了基礎(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域?yàn)橐粋€(gè)int類型的數(shù)組的引用變量。對(duì)應(yīng)引用類型,寫final域的重排序?qū)幾g器和處理器增加了如下約束:

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

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

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

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

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

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

 

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

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

其實(shí)這需要另一個(gè)條件:在構(gòu)造函數(shù)內(nèi)部,不能讓這個(gè)被構(gòu)造對(duì)象的引用被其它線程所見。也就是對(duì)象引用不能在構(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)致對(duì)象還未完成構(gòu)造前就對(duì)線程B可見了。因?yàn)?和2允許重排序,所以線程B可能無法看到final域被正確初始化后的值。實(shí)際執(zhí)行的時(shí)序圖可能如下所示:

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

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

總結(jié):

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

 

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

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

在編譯器中會(huì)存在如下的處理:

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

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

 

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

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

總結(jié):

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

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

原文鏈接:https://juejin.cn/post/7017979592119943198

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 成人不卡一区二区 | 国产一国产一级毛片视频在线 | 天天鲁在线视频免费观看 | 国产精品成人久久久久a级 男女无遮挡羞羞视频 | 成人毛片100部免费观看 | 欧美人xx | 欧美精品一区自拍a毛片在线视频 | 黑人三级毛片 | 99影视电影电视剧在线播放 | 在线观看中文字幕av | 国产无限资源在线观看 | 欧美巨乳在线观看 | 亚洲第一成av人网站懂色 | 在线看小早川怜子av | av免费在线观看国产 | 亚洲综合视频一区 | 日本一区免费看 | 福利免费视频 | 欧美精品在线免费观看 | 亚洲欧美日韩精品久久 | 草逼一区 | 日韩一级成人 | 亚洲极色 | 国产免费小视频在线观看 | 中文字幕网站在线 | 成人午夜视频免费在线观看 | 久久久久在线观看 | 久久国产精品久久精品国产演员表 | 成人不卡一区二区 | 久久最新网址 | 泰剧19禁啪啪无遮挡大尺度 | 激情91 | 免费一级特黄毛片视频 | 国产成人高清在线观看 | 日本一级黄色大片 | 国产精品成人一区 | 亚洲一区国产一区 | 91成人在线免费观看 | 久久99精品久久久久久秒播蜜臀 | 一级黄色免费大片 | av亚洲在线观看 |