日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪(fǎng)問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > java >内容正文

java

什么?面试官问我Java内存模型!这不得给我加薪?

發(fā)布時(shí)間:2023/12/4 java 69 豆豆
生活随笔 收集整理的這篇文章主要介紹了 什么?面试官问我Java内存模型!这不得给我加薪? 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

內(nèi)存模型的基礎(chǔ)

  • 通信  線(xiàn)程之間以何種機(jī)制來(lái)交換信息
    • 共享內(nèi)存  隱式通信
    • 消息傳遞  顯示通信
  • 同步  程序中用于控制不同線(xiàn)程間操作,發(fā)生的相對(duì)順序的機(jī)制
    • 共享內(nèi)存  顯式同步
    • 消息傳遞  隱式同步

Java線(xiàn)程線(xiàn)程之間是通過(guò)共享內(nèi)存的方式實(shí)現(xiàn)通信的.

內(nèi)存模型的抽象結(jié)構(gòu)

  • 共享變量

共享變量手內(nèi)存模型影響,線(xiàn)程會(huì)去主內(nèi)存里去加載共享變量,當(dāng)線(xiàn)程需要改變共享變量時(shí),會(huì)將本地內(nèi)存已更改的副本提交到主內(nèi)存.

  • 局部變量

局部變量不會(huì)受內(nèi)存模型的影響

線(xiàn)程之間通信

指令重排

  • 編譯器優(yōu)化的重排序
  • 指令級(jí)并行的重排序
  • 內(nèi)存系統(tǒng)的重排序

什么是指令重排?

int i=0; 2 int j=1;

按照我們的認(rèn)知,程序是一行一行往下執(zhí)行的,但是由于編譯器或運(yùn)行時(shí)環(huán)境為了優(yōu)化程序性能,采取對(duì)指令進(jìn)行重新排序執(zhí)行,也就是說(shuō)在計(jì)算機(jī)執(zhí)行上面兩句話(huà)的時(shí)候,有可能第二條語(yǔ)句會(huì)優(yōu)先于第一條語(yǔ)句執(zhí)行.

然而并不是所有的指令都能重排,重排需要基于數(shù)據(jù)依賴(lài)性.

數(shù)據(jù)依賴(lài)性

如果兩個(gè)操作訪(fǎng)問(wèn)同一個(gè)變量,且這兩個(gè)操作中有一個(gè)為寫(xiě)操作,此時(shí)這兩個(gè)操作之間就存在數(shù)據(jù)依賴(lài)性。數(shù)據(jù)依賴(lài)分下列三種類(lèi)型:

名稱(chēng)代碼示例說(shuō)明
寫(xiě)后讀a=1;b=a;寫(xiě)一個(gè)變量之后,再讀這個(gè)位置.
寫(xiě)后寫(xiě)a=1;a=2;寫(xiě)一個(gè)變量之后,再寫(xiě)這個(gè)變量.
讀后寫(xiě)a=b;b=1;讀一個(gè)變量之后,再寫(xiě)這個(gè)變量.

上面的情況,如果重排序了兩個(gè)操作的執(zhí)行順序,程序的執(zhí)行結(jié)果將會(huì)跟預(yù)期完全不一樣.

所以說(shuō),雖然編譯器和處理器可能會(huì)對(duì)操作做重排序,但是編譯器和處理器在重排序時(shí),會(huì)遵守?cái)?shù)據(jù)依賴(lài)性,編譯器和處理器不會(huì)改變存在數(shù)據(jù)依賴(lài)關(guān)系的兩個(gè)操作的執(zhí)行順序。

注意,這里所說(shuō)的數(shù)據(jù)依賴(lài)性?xún)H針對(duì)單個(gè)處理器中執(zhí)行的指令序列和單個(gè)線(xiàn)程中執(zhí)行的操作,不同處理器之間和不同線(xiàn)程之間的數(shù)據(jù)依賴(lài)性不被編譯器和處理器考慮。

as-if-serial

定義:不管怎么重排序(編譯器和處理器為了提?并?度),(單線(xiàn)程) 程序的執(zhí)?結(jié)果不能被改變。編譯器、runtime和處理器都必須遵守as-if-serial語(yǔ)義。

happens-before

happens-before是JMM的最核心概念之一

JMM設(shè)計(jì)意圖

  • 程序員對(duì)內(nèi)存模型的使用
    • 為程序員提供足夠強(qiáng)的內(nèi)存可見(jiàn)性保證
  • 編譯器和處理器對(duì)內(nèi)存模型的實(shí)現(xiàn)
    • 對(duì)編譯器和處理器的限制要盡可能的放松

JMM禁止:

禁止編譯器和處理器會(huì)改變程序執(zhí)行結(jié)果的重排序.

JMM允許:

允許編譯器和處理器不會(huì)改變程序執(zhí)行結(jié)果的重排序.

happens-before規(guī)則

在JMM中,如果?個(gè)操作執(zhí)?的結(jié)果需要對(duì)另?個(gè)操作可?,那么這兩個(gè)操作之間必須要存在happens-before關(guān)系.

  • 程序順序規(guī)則  ?個(gè)線(xiàn)程中的每個(gè)操作,happens-before于該線(xiàn)程中的任意后續(xù)操作.
  • 監(jiān)視器鎖規(guī)則  對(duì)?個(gè)鎖的解鎖,happens-before于隨后對(duì)這個(gè)鎖的加鎖.
  • volatile變量規(guī)則  對(duì)?個(gè)volatile域的寫(xiě),happens-before于任意后續(xù)對(duì)這個(gè)volatile域的讀.
  • 傳遞性  如果A happens-before B,且B happens-before C,那么A happens-before C.
  • start()規(guī)則  如果線(xiàn)程A執(zhí)?操作ThreadB.start()(啟動(dòng)線(xiàn)程B),那么A線(xiàn)程的ThreadB.start()操作happens-before于線(xiàn)程B中的任意操作。
  • join()規(guī)則  如果線(xiàn)程A執(zhí)?操作ThreadB.join()并成功返回,那么線(xiàn)程B中的任意操作happens-before于線(xiàn)程A從ThreadB.join()返回
  • 線(xiàn)程中斷規(guī)則  對(duì)線(xiàn)程interrupt?法的調(diào)?happens-before于被中斷線(xiàn)程的代碼檢測(cè)到中斷事件的發(fā)?.
  • 對(duì)象終結(jié)規(guī)則  ?個(gè)對(duì)象的初始化的完成,也就是構(gòu)造函數(shù)執(zhí)?的結(jié)束?定 happens-before它的finalize()?法.

JMM向程序員提供的happens-before規(guī)則能滿(mǎn)?程序員的需求.

JMM對(duì)編譯器和處理器的束縛已經(jīng)盡可能少.

JMM對(duì)程序員的承諾

如果?個(gè)操作happens-before另?個(gè)操作,那么第?個(gè)操作的執(zhí)?結(jié)果將對(duì)第?個(gè)操作 可?,?且第?個(gè)操作的執(zhí)?順序排在第?個(gè)操作之前.

JMM對(duì)編譯器和處理器重排序的約束原則

兩個(gè)操作之間存在happens-before關(guān)系,并不意味著Java平臺(tái)的具體實(shí)現(xiàn)必須要按照 happens-before關(guān)系指定的順序來(lái)執(zhí)?.

例子:

1 public class Demo29 { 2 int a=0; 3 boolean flag=false; 4 public void writer(){ 5 a=1; //1 6 flag=true; //2 7 } 8 public void reader(){ 9 if(flag){ //3 10 int i=a * a; //4 11 } 12 } 13 }

假如線(xiàn)程B在進(jìn)行操作4時(shí),能否看到線(xiàn)程A在操作1對(duì)共享變量a的寫(xiě)入呢? 不一定

| 時(shí)刻 | 線(xiàn)程A | 線(xiàn)程B | | T1 | flag=true | | | T2 | | if(flag) | | T3 | | int i=a*a | | T4 | a=1 | |

當(dāng)線(xiàn)程A在執(zhí)行writer方法時(shí),因?yàn)橹噶钪嘏判?會(huì)先執(zhí)行flag=true,再執(zhí)行a=1.而線(xiàn)程B在執(zhí)行操作4時(shí)就會(huì)讀不到線(xiàn)程A對(duì)共享變量a的寫(xiě)入,導(dǎo)致運(yùn)行結(jié)果超出預(yù)期.

解決方案1:

通過(guò)加鎖的方式來(lái)解決

1 public class Demo29 { 2 int a=0; 3 boolean flag=false; 4 public synchronized void writer(){ 5 a=1; //1 6 flag=true; //2 7 } 8 public synchronized void reader(){ 9 if(flag){ //3 10 int i=a * a; //4 11 } 12 } 13 }

鎖的內(nèi)存語(yǔ)義:

  • 線(xiàn)程A釋放?個(gè)鎖,實(shí)質(zhì)上是線(xiàn)程A向接下來(lái)將要獲取這個(gè)鎖的某個(gè)線(xiàn)程發(fā)出了(線(xiàn)程A 對(duì)共享變量所做修改的)消息。
  • 線(xiàn)程B獲取?個(gè)鎖,實(shí)質(zhì)上是線(xiàn)程B接收了之前某個(gè)線(xiàn)程發(fā)出的(在釋放這個(gè)鎖之前對(duì)共 享變量所做修改的)消息。
  • 線(xiàn)程A釋放鎖,隨后線(xiàn)程B獲取這個(gè)鎖,這個(gè)過(guò)程實(shí)質(zhì)上是線(xiàn)程A通過(guò)主內(nèi)存向線(xiàn)程B發(fā) 送消息。

volatile的作用

  • volatile原理:被volatile關(guān)鍵字修飾的變量,編譯器與運(yùn)行時(shí)都會(huì)注意到這個(gè)變量是共享的,因此不會(huì)將該變量上的操作與其他內(nèi)存操作一起重排序。volatile變量不會(huì)被緩存在寄存器或者對(duì)其他處理器不可見(jiàn)的地方,因此在讀取volatile類(lèi)型的變量時(shí)總會(huì)返回最新寫(xiě)入的值。
  • 在訪(fǎng)問(wèn)volatile變量時(shí)不會(huì)執(zhí)行加鎖操作,因此也就不會(huì)使執(zhí)行線(xiàn)程阻塞,因此volatile變量是一種比sychronized關(guān)鍵字更輕量級(jí)的同步機(jī)制。當(dāng)對(duì)非 volatile 變量進(jìn)行讀寫(xiě)的時(shí)候,每個(gè)線(xiàn)程先從內(nèi)存拷貝變量到CPU緩存中。如果計(jì)算機(jī)有多個(gè)CPU,每個(gè)線(xiàn)程可能在不同的CPU上被處理,這意味著每個(gè)線(xiàn)程可以拷貝到不同的 CPU cache 中。而聲明變量是 volatile 的,JVM 保證了每次讀變量都從內(nèi)存中讀,跳過(guò) CPU cache 這一步。
  • volatile在Java并發(fā)編程中常用于保持內(nèi)存可見(jiàn)性和防止指令重排序。內(nèi)存可見(jiàn)性(Memory Visibility):所有線(xiàn)程都能看到共享內(nèi)存的最新?tīng)顟B(tài);防止指令重排:在基于偏序關(guān)系的Happens-Before內(nèi)存模型中,指令重排技術(shù)大大提高了程序執(zhí)行效率,但同時(shí)也引入了一些問(wèn)題。
  • 可見(jiàn)性:volatile保持內(nèi)存可見(jiàn)性的特殊規(guī)則:read、load、use動(dòng)作必須連續(xù)出現(xiàn);assign、store、write動(dòng)作必須連續(xù)出現(xiàn);每次讀取前必須先從主內(nèi)存刷新最新的值;每次寫(xiě)入后必須立即同步回主內(nèi)存當(dāng)中。也就是說(shuō),volatile關(guān)鍵字修飾的變量看到的隨時(shí)是自己的最新值。在線(xiàn)程1中對(duì)變量v的最新修改,對(duì)線(xiàn)程2是可見(jiàn)的。
  • 內(nèi)存屏障:volatile防止指令重排的策略:在每個(gè)volatile寫(xiě)操作的前面插入一個(gè)StoreStore屏障;在每個(gè)volatile寫(xiě)操作的后面插入一個(gè)StoreLoad屏障;在每個(gè)volatile讀操作的后面插入一個(gè)LoadLoad屏障;在每個(gè)volatile讀操作的后面插入一個(gè)LoadStore屏障。
  • volatile 性能:volatile 的讀性能消耗與普通變量幾乎相同,但是寫(xiě)操作稍慢,因?yàn)樗枰诒镜卮a中插入許多內(nèi)存屏障指令來(lái)保證處理器不發(fā)生亂序執(zhí)行。

  • volatile內(nèi)存語(yǔ)義

    • 線(xiàn)程A寫(xiě)?個(gè)volatile變量,實(shí)質(zhì)上是線(xiàn)程A向接下來(lái)將要讀這個(gè)volatile變量的某個(gè)線(xiàn)程 發(fā)出了(其對(duì)共享變量所做修改的)消息。
    • 線(xiàn)程B讀?個(gè)volatile變量,實(shí)質(zhì)上是線(xiàn)程B接收了之前某個(gè)線(xiàn)程發(fā)出的(在寫(xiě)這個(gè)volatile 變量之前對(duì)共享變量所做修改的)消息。
    • 線(xiàn)程A寫(xiě)?個(gè)volatile變量,隨后線(xiàn)程B讀這個(gè)volatile變量,這個(gè)過(guò)程實(shí)質(zhì)上是線(xiàn)程A通過(guò) 主內(nèi)存向線(xiàn)程B發(fā)送消息。

    volatile內(nèi)存語(yǔ)義的實(shí)現(xiàn)

    是否能重排序第二個(gè)操作
    第一個(gè)操作普通讀/寫(xiě)
    普通讀/寫(xiě)Y
    volatile讀N
    volatile寫(xiě)Y
    • 當(dāng)?shù)?個(gè)操作是volatile寫(xiě)時(shí),不管第?個(gè)操作是什么,都不能重排序。

    • 當(dāng)?shù)?個(gè)操作是volatile讀時(shí),不管第?個(gè)操作是什么,都不能重排序。

    • 當(dāng)?shù)?個(gè)操作是volatile寫(xiě),第?個(gè)操作是volatile讀時(shí),不能重排序。

    內(nèi)存屏障

    屏障類(lèi)型指令示例說(shuō)明
    LoadLoad BarriersLoad1;LoadLoad;Load2確保Load1數(shù)據(jù)的裝載先于Load2及所有后續(xù)裝載指令的裝載
    StoreStore BarriersStore1;StoreStore;Store2確保Store1數(shù)據(jù)對(duì)其他處理器可見(jiàn)(刷新達(dá)到內(nèi)存)先于Store2及所有后續(xù)存儲(chǔ)指令的存儲(chǔ)
    LoadStore BarriersLoad1;LoadStrore;Store2確保Load1數(shù)據(jù)裝載先于Store2及所有后續(xù)的存儲(chǔ)指令刷新到內(nèi)存
    StoreLoad BarriersStore;StoreLoad;Load2確保Store1數(shù)據(jù)對(duì)其他處理器變得可見(jiàn)(指刷新到內(nèi)存)先于Load2及所有后續(xù)裝載指令的裝載.StoreLoad Barriers會(huì)使該屏障之前的所有內(nèi)存訪(fǎng)問(wèn)指令(存儲(chǔ)和裝載指令)完成之后,才執(zhí)行該屏障之后的內(nèi)存訪(fǎng)問(wèn)指令
    • 在每個(gè)volatile寫(xiě)操作的前?插??個(gè)StoreStore屏障
    • 在每個(gè)volatile寫(xiě)操作的后?插??個(gè)StoreLoad屏障
    • 在每個(gè)volatile讀操作的后?插??個(gè)LoadLoad屏障
    • 在每個(gè)volatile讀操作的后?插??個(gè)LoadStore屏障

    Final的內(nèi)存語(yǔ)義

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

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

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

    • 在?個(gè)線(xiàn)程中,初次讀對(duì)象引?與初次讀該對(duì)象包含的final域,JMM禁?處理器重排序這兩個(gè)操作
    • 在構(gòu)造函數(shù)內(nèi)對(duì)一個(gè)final引用的對(duì)象的成員域的寫(xiě)入,與隨后在構(gòu)造函數(shù)外把這個(gè)被構(gòu)造對(duì)象的引用賦值給一個(gè)引用變量,這兩個(gè)操作之間不能重排序

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

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

    多線(xiàn)程下的單例模式

    雙重檢查鎖定

    1 public class DoubleCheckedLocking {2 private static DoubleCheckedLocking doubleCheckedLocking;3 4 private DoubleCheckedLocking() {5 6 }7 8 public static DoubleCheckedLocking getInstance() { 9 if (doubleCheckedLocking == null) { 10 synchronized (DoubleCheckedLocking.class) { 11 if (doubleCheckedLocking == null) { 12 doubleCheckedLocking = new DoubleCheckedLocking();//問(wèn)題出現(xiàn)在這里 13 } 14 } 15 } 16 return doubleCheckedLocking; 17 } 18 }

    我們來(lái)看看這段雙重檢查鎖定的單例模式有什么問(wèn)題?

    線(xiàn)程A設(shè)置指向剛分配的內(nèi)存地址后,線(xiàn)程B就判斷doubleCheckedLocking對(duì)象是否為空,然后直接返回未初始化的doubleCheckedLocking對(duì)象,這樣會(huì)引發(fā)出很?chē)?yán)重的問(wèn)題.

    解決方案1:

    使用volatile,禁止2和3重排序

    1 public class DoubleCheckedLocking { 2 private volatile static DoubleCheckedLocking doubleCheckedLocking; 3 4 private DoubleCheckedLocking() { 5 6 } 7 8 public static DoubleCheckedLocking getInstance() { 9 if (doubleCheckedLocking == null) { 10 synchronized (DoubleCheckedLocking.class) { 11 if (doubleCheckedLocking == null) { 12 doubleCheckedLocking = new DoubleCheckedLocking();//問(wèn)題出現(xiàn)在這里 13 } 14 } 15 } 16 return doubleCheckedLocking; 17 } 18 }

    解決方案2:

    基于類(lèi)初始化,允許2和3重排序,但不允許其他線(xiàn)程"看到這個(gè)重排序"

    1 public class InstanceFactory { 2 private static class InstanceHolder { 3 public static DoubleCheckedLocking doubleCheckedLocking = new DoubleCheckedLocking(); 4 } 5 6 public static DoubleCheckedLocking getInstance() { 7 return InstanceHolder.doubleCheckedLocking; 8 } 9 }

    這里使用到了靜態(tài)內(nèi)部類(lèi)的靜態(tài)屬性,類(lèi)的靜態(tài)屬性只會(huì)在第一次調(diào)用的時(shí)候初始化,而且會(huì)有一個(gè)Class對(duì)象的初始化鎖,從而確保只會(huì)發(fā)生一次初始化.

    最后,祝大家早日學(xué)有所成,拿到滿(mǎn)意offer

    創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)

    總結(jié)

    以上是生活随笔為你收集整理的什么?面试官问我Java内存模型!这不得给我加薪?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。