亚洲最大看欧美片,亚洲图揄拍自拍另类图片,欧美精品v国产精品v呦,日本在线精品视频免费

  • 站長(zhǎng)資訊網(wǎng)
    最全最豐富的資訊網(wǎng)站

    當(dāng)面試官問(wèn)你什么是JMM

    當(dāng)面試官問(wèn)你什么是JMM

    相關(guān)學(xué)習(xí)推薦:java基礎(chǔ)

    思維導(dǎo)圖

    當(dāng)面試官問(wèn)你什么是JMM

    面試官:講講什么是JMM

    你要是整這個(gè)我可就不困了。

    當(dāng)面試官問(wèn)你什么是JMM

    JMM就是Java內(nèi)存模型(java memory model)。因?yàn)樵诓煌挠布a(chǎn)商和不同的操作系統(tǒng)下,內(nèi)存的訪(fǎng)問(wèn)有一定的差異,所以會(huì)造成相同的代碼運(yùn)行在不同的系統(tǒng)上會(huì)出現(xiàn)各種問(wèn)題。所以java內(nèi)存模型(JMM)屏蔽掉各種硬件和操作系統(tǒng)的內(nèi)存訪(fǎng)問(wèn)差異,以實(shí)現(xiàn)讓java程序在各種平臺(tái)下都能達(dá)到一致的并發(fā)效果。

    Java內(nèi)存模型規(guī)定所有的變量都存儲(chǔ)在主內(nèi)存中,包括實(shí)例變量,靜態(tài)變量,但是不包括局部變量和方法參數(shù)。每個(gè)線(xiàn)程都有自己的工作內(nèi)存,線(xiàn)程的工作內(nèi)存保存了該線(xiàn)程用到的變量和主內(nèi)存的副本拷貝,線(xiàn)程對(duì)變量的操作都在工作內(nèi)存中進(jìn)行線(xiàn)程不能直接讀寫(xiě)主內(nèi)存中的變量。

    不同的線(xiàn)程之間也無(wú)法訪(fǎng)問(wèn)對(duì)方工作內(nèi)存中的變量。線(xiàn)程之間變量值的傳遞均需要通過(guò)主內(nèi)存來(lái)完成。

    如果聽(tīng)起來(lái)抽象的話(huà),我可以畫(huà)張圖給你看看,會(huì)直觀一點(diǎn):

    當(dāng)面試官問(wèn)你什么是JMM

    每個(gè)線(xiàn)程的工作內(nèi)存都是獨(dú)立的,線(xiàn)程操作數(shù)據(jù)只能在工作內(nèi)存中進(jìn)行,然后刷回到主存。這是 Java 內(nèi)存模型定義的線(xiàn)程基本工作方式。

    溫馨提醒一下,這里有些人會(huì)把Java內(nèi)存模型誤解為Java內(nèi)存結(jié)構(gòu),然后答到堆,棧,GC垃圾回收,最后和面試官想問(wèn)的問(wèn)題相差甚遠(yuǎn)。實(shí)際上一般問(wèn)到Java內(nèi)存模型都是想問(wèn)多線(xiàn)程,Java并發(fā)相關(guān)的問(wèn)題。

    面試官:那JMM定義了什么

    這個(gè)簡(jiǎn)單,整個(gè)Java內(nèi)存模型實(shí)際上是圍繞著三個(gè)特征建立起來(lái)的。分別是:原子性,可見(jiàn)性,有序性。這三個(gè)特征可謂是整個(gè)Java并發(fā)的基礎(chǔ)。

    原子性

    原子性指的是一個(gè)操作是不可分割,不可中斷的,一個(gè)線(xiàn)程在執(zhí)行時(shí)不會(huì)被其他線(xiàn)程干擾。

    面試官拿筆寫(xiě)了段代碼,下面這幾句代碼能保證原子性嗎

    int i = 2;int j = i; i++; i = i + 1;復(fù)制代碼

    第一句是基本類(lèi)型賦值操作,必定是原子性操作。

    第二句先讀取i的值,再賦值到j(luò),兩步操作,不能保證原子性。

    第三和第四句其實(shí)是等效的,先讀取i的值,再+1,最后賦值到i,三步操作了,不能保證原子性。

    JMM只能保證基本的原子性,如果要保證一個(gè)代碼塊的原子性,提供了monitorenter 和 moniterexit 兩個(gè)字節(jié)碼指令,也就是 synchronized 關(guān)鍵字。因此在 synchronized 塊之間的操作都是原子性的。

    可見(jiàn)性

    可見(jiàn)性指當(dāng)一個(gè)線(xiàn)程修改共享變量的值,其他線(xiàn)程能夠立即知道被修改了。Java是利用volatile關(guān)鍵字來(lái)提供可見(jiàn)性的。 當(dāng)變量被volatile修飾時(shí),這個(gè)變量被修改后會(huì)立刻刷新到主內(nèi)存,當(dāng)其它線(xiàn)程需要讀取該變量時(shí),會(huì)去主內(nèi)存中讀取新值。而普通變量則不能保證這一點(diǎn)。

    除了volatile關(guān)鍵字之外,final和synchronized也能實(shí)現(xiàn)可見(jiàn)性。

    synchronized的原理是,在執(zhí)行完,進(jìn)入unlock之前,必須將共享變量同步到主內(nèi)存中。

    final修飾的字段,一旦初始化完成,如果沒(méi)有對(duì)象逸出(指對(duì)象為初始化完成就可以被別的線(xiàn)程使用),那么對(duì)于其他線(xiàn)程都是可見(jiàn)的。

    有序性

    在Java中,可以使用synchronized或者volatile保證多線(xiàn)程之間操作的有序性。實(shí)現(xiàn)原理有些區(qū)別:

    volatile關(guān)鍵字是使用內(nèi)存屏障達(dá)到禁止指令重排序,以保證有序性。

    synchronized的原理是,一個(gè)線(xiàn)程lock之后,必須unlock后,其他線(xiàn)程才可以重新lock,使得被synchronized包住的代碼塊在多線(xiàn)程之間是串行執(zhí)行的。

    面試官:給我講一下八種內(nèi)存交互操作吧

    好的,面試官,內(nèi)存交互操作有8種,我畫(huà)張圖給你看吧:

    當(dāng)面試官問(wèn)你什么是JMM
    • lock(鎖定),作用于主內(nèi)存中的變量,把變量標(biāo)識(shí)為線(xiàn)程獨(dú)占的狀態(tài)。
    • read(讀取),作用于主內(nèi)存的變量,把變量的值從主內(nèi)存?zhèn)鬏數(shù)骄€(xiàn)程的工作內(nèi)存中,以便下一步的load操作使用。
    • load(加載),作用于工作內(nèi)存的變量,把read操作主存的變量放入到工作內(nèi)存的變量副本中。
    • use(使用),作用于工作內(nèi)存的變量,把工作內(nèi)存中的變量傳輸?shù)綀?zhí)行引擎,每當(dāng)虛擬機(jī)遇到一個(gè)需要使用到變量的值的字節(jié)碼指令時(shí)將會(huì)執(zhí)行這個(gè)操作。
    • assign(賦值),作用于工作內(nèi)存的變量,它把一個(gè)從執(zhí)行引擎中接受到的值賦值給工作內(nèi)存的變量副本中,每當(dāng)虛擬機(jī)遇到一個(gè)給變量賦值的字節(jié)碼指令時(shí)將會(huì)執(zhí)行這個(gè)操作。
    • store(存儲(chǔ)),作用于工作內(nèi)存的變量,它把一個(gè)從工作內(nèi)存中一個(gè)變量的值傳送到主內(nèi)存中,以便后續(xù)的write使用。
    • write(寫(xiě)入):作用于主內(nèi)存中的變量,它把store操作從工作內(nèi)存中得到的變量的值放入主內(nèi)存的變量中。
    • unlock(解鎖):作用于主內(nèi)存的變量,它把一個(gè)處于鎖定狀態(tài)的變量釋放出來(lái),釋放后的變量才可以被其他線(xiàn)程鎖定。

    我再補(bǔ)充一下JMM對(duì)8種內(nèi)存交互操作制定的規(guī)則吧:

    • 不允許read、load、store、write操作之一單獨(dú)出現(xiàn),也就是read操作后必須load,store操作后必須write。
    • 不允許線(xiàn)程丟棄他最近的assign操作,即工作內(nèi)存中的變量數(shù)據(jù)改變了之后,必須告知主存。
    • 不允許線(xiàn)程將沒(méi)有assign的數(shù)據(jù)從工作內(nèi)存同步到主內(nèi)存。
    • 一個(gè)新的變量必須在主內(nèi)存中誕生,不允許工作內(nèi)存直接使用一個(gè)未被初始化的變量。就是對(duì)變量實(shí)施use、store操作之前,必須經(jīng)過(guò)load和assign操作。
    • 一個(gè)變量同一時(shí)間只能有一個(gè)線(xiàn)程對(duì)其進(jìn)行l(wèi)ock操作。多次lock之后,必須執(zhí)行相同次數(shù)unlock才可以解鎖。
    • 如果對(duì)一個(gè)變量進(jìn)行l(wèi)ock操作,會(huì)清空所有工作內(nèi)存中此變量的值。在執(zhí)行引擎使用這個(gè)變量前,必須重新load或assign操作初始化變量的值。
    • 如果一個(gè)變量沒(méi)有被lock,就不能對(duì)其進(jìn)行unlock操作。也不能unlock一個(gè)被其他線(xiàn)程鎖住的變量。
    • 一個(gè)線(xiàn)程對(duì)一個(gè)變量進(jìn)行unlock操作之前,必須先把此變量同步回主內(nèi)存。

    面試官:講一下volatile關(guān)鍵字吧

    內(nèi)心:這可以重頭戲呀,可不能出岔子~

    很多并發(fā)編程都使用了volatile關(guān)鍵字,主要的作用包括兩點(diǎn):

    1. 保證線(xiàn)程間變量的可見(jiàn)性。
    2. 禁止CPU進(jìn)行指令重排序。

    可見(jiàn)性

    volatile修飾的變量,當(dāng)一個(gè)線(xiàn)程改變了該變量的值,其他線(xiàn)程是立即可見(jiàn)的。普通變量則需要重新讀取才能獲得最新值。

    volatile保證可見(jiàn)性的流程大概就是這個(gè)一個(gè)過(guò)程:

    當(dāng)面試官問(wèn)你什么是JMM

    volatile一定能保證線(xiàn)程安全嗎

    先說(shuō)結(jié)論吧,volatile不能一定能保證線(xiàn)程安全。

    怎么證明呢,我們看下面一段代碼的運(yùn)行結(jié)果就知道了:

    /**  * @author Ye Hongzhi 公眾號(hào):java技術(shù)愛(ài)好者  **/public class VolatileTest extends Thread {    private static volatile int count = 0;    public static void main(String[] args) throws Exception {         Vector<Thread> threads = new Vector<>();        for (int i = 0; i < 100; i++) {             VolatileTest thread = new VolatileTest();             threads.add(thread);             thread.start();         }        //等待子線(xiàn)程全部完成         for (Thread thread : threads) {             thread.join();         }        //輸出結(jié)果,正確結(jié)果應(yīng)該是1000,實(shí)際卻是984         System.out.println(count);//984     }    @Override     public void run() {        for (int i = 0; i < 10; i++) {            try {                //休眠500毫秒                 Thread.sleep(500);             } catch (Exception e) {                 e.printStackTrace();             }             count++;         }     } }復(fù)制代碼

    為什么volatile不能保證線(xiàn)程安全?

    很簡(jiǎn)單呀,可見(jiàn)性不能保證操作的原子性,前面說(shuō)過(guò)了count++不是原子性操作,會(huì)當(dāng)做三步,先讀取count的值,然后+1,最后賦值回去count變量。需要保證線(xiàn)程安全的話(huà),需要使用synchronized關(guān)鍵字或者lock鎖,給count++這段代碼上鎖:

    private static synchronized void add() {     count++; }復(fù)制代碼

    禁止指令重排序

    首先要講一下as-if-serial語(yǔ)義,不管怎么重排序,(單線(xiàn)程)程序的執(zhí)行結(jié)果不能被改變。

    為了使指令更加符合CPU的執(zhí)行特性,最大限度的發(fā)揮機(jī)器的性能,提高程序的執(zhí)行效率,只要程序的最終結(jié)果與它順序化情況的結(jié)果相等,那么指令的執(zhí)行順序可以與代碼邏輯順序不一致,這個(gè)過(guò)程就叫做指令的重排序。

    重排序的種類(lèi)分為三種,分別是:編譯器重排序,指令級(jí)并行的重排序,內(nèi)存系統(tǒng)重排序。整個(gè)過(guò)程如下所示:

    當(dāng)面試官問(wèn)你什么是JMM

    指令重排序在單線(xiàn)程是沒(méi)有問(wèn)題的,不會(huì)影響執(zhí)行結(jié)果,而且還提高了性能。但是在多線(xiàn)程的環(huán)境下就不能保證一定不會(huì)影響執(zhí)行結(jié)果了。

    所以在多線(xiàn)程環(huán)境下,就需要禁止指令重排序

    volatile關(guān)鍵字禁止指令重排序有兩層意思:

    • 當(dāng)程序執(zhí)行到volatile變量的讀操作或者寫(xiě)操作時(shí),在其前面的操作的更改肯定全部已經(jīng)進(jìn)行,且結(jié)果已經(jīng)對(duì)后面的操作可見(jiàn),在其后面的操作肯定還沒(méi)有進(jìn)行。

    • 在進(jìn)行指令優(yōu)化時(shí),不能將在對(duì)volatile變量訪(fǎng)問(wèn)的語(yǔ)句放在其后面執(zhí)行,也不能把volatile變量后面的語(yǔ)句放到其前面執(zhí)行。

    下面舉個(gè)例子:

    private static int a;//非volatile修飾變量private static int b;//非volatile修飾變量private static volatile int k;//volatile修飾變量private void hello() {     a = 1;  //語(yǔ)句1     b = 2;  //語(yǔ)句2     k = 3;  //語(yǔ)句3     a = 4;  //語(yǔ)句4     b = 5;  //語(yǔ)句5     //以下省略...}復(fù)制代碼

    變量a,b是非volatile修飾的變量,k則使用volatile修飾。所以語(yǔ)句3不能放在語(yǔ)句1、2前,也不能放在語(yǔ)句4、5后。但是語(yǔ)句1、2的順序是不能保證的,同理,語(yǔ)句4、5也不能保證順序。

    并且,執(zhí)行到語(yǔ)句3的時(shí)候,語(yǔ)句1,2是肯定執(zhí)行完畢的,而且語(yǔ)句1,2的執(zhí)行結(jié)果對(duì)于語(yǔ)句3,4,5是可見(jiàn)的。

    volatile禁止指令重排序的原理是什么

    首先要講一下內(nèi)存屏障,內(nèi)存屏障可以分為以下幾類(lèi):

    • LoadLoad 屏障:對(duì)于這樣的語(yǔ)句Load1,LoadLoad,Load2。在Load2及后續(xù)讀取操作要讀取的數(shù)據(jù)被訪(fǎng)問(wèn)前,保證Load1要讀取的數(shù)據(jù)被讀取完畢。

    • StoreStore屏障:對(duì)于這樣的語(yǔ)句Store1, StoreStore, Store2,在Store2及后續(xù)寫(xiě)入操作執(zhí)行前,保證Store1的寫(xiě)入操作對(duì)其它處理器可見(jiàn)。

    • LoadStore 屏障:對(duì)于這樣的語(yǔ)句Load1, LoadStore,Store2,在Store2及后續(xù)寫(xiě)入操作被刷出前,保證Load1要讀取的數(shù)據(jù)被讀取完畢。

    • StoreLoad 屏障:對(duì)于這樣的語(yǔ)句Store1, StoreLoad,Load2,在Load2及后續(xù)所有讀取操作執(zhí)行前,保證Store1的寫(xiě)入對(duì)所有處理器可見(jiàn)。

    在每個(gè)volatile讀操作后插入LoadLoad屏障,在讀操作后插入LoadStore屏障。

    當(dāng)面試官問(wèn)你什么是JMM

    在每個(gè)volatile寫(xiě)操作的前面插入一個(gè)StoreStore屏障,后面插入一個(gè)SotreLoad屏障。

    當(dāng)面試官問(wèn)你什么是JMM

    大概的原理就是這樣。

    面試官:講得還不錯(cuò),基本上都講到了,時(shí)間也不早了,今天的面試就到這吧,回去等通知吧~

    總結(jié)

    要學(xué)習(xí)并發(fā)編程,java內(nèi)存模型是第一站了。原子性,有序性,可見(jiàn)性這三大特征幾乎貫穿了并發(fā)編程,可謂是基礎(chǔ)知識(shí)。對(duì)于后面要深入學(xué)習(xí)起到鋪墊作用。

    在這篇文章中,如果面試的話(huà),重點(diǎn)是Java內(nèi)存模型(JMM)的工作方式,三大特征,還有volatile關(guān)鍵字。為什么喜歡問(wèn)volatile關(guān)鍵字呢,因?yàn)?strong>volatile關(guān)鍵字可以扯出很多東西,比如可見(jiàn)性,有序性,還有內(nèi)存屏障等等??梢砸会樢?jiàn)血地看出面試者的技術(shù)水平,畢竟面試官也想高效地篩選出符合要求的人才嘛。

    贊(0)
    分享到: 更多 (0)
    網(wǎng)站地圖   滬ICP備18035694號(hào)-2    滬公網(wǎng)安備31011702889846號(hào)