Java內(nèi)存模型和volatile、synchronized

前言

  1. 先說(shuō)說(shuō)計(jì)算機(jī)緩存:計(jì)算機(jī)在執(zhí)行程序的時(shí)候,都是通過(guò)CPU來(lái)執(zhí)行指令,當(dāng)然執(zhí)行一串指令少不了需要某些數(shù)據(jù),這些數(shù)據(jù)就在主內(nèi)存中(物理內(nèi)存)。隨著科技不斷發(fā)展,CPU執(zhí)行速度越來(lái)越快,但內(nèi)存存取發(fā)展并沒(méi)有跟上CPU飛速發(fā)展的腳步,導(dǎo)致性能瓶頸出現(xiàn)在了內(nèi)存存取上,所以這個(gè)時(shí)候出現(xiàn)了緩存技術(shù)來(lái)加快數(shù)據(jù)的存取。
  2. 在程序真正運(yùn)行時(shí),會(huì)將運(yùn)算需要的數(shù)據(jù)從主存復(fù)制一份到CPU的高速緩存當(dāng)中,那么CPU進(jìn)行計(jì)算時(shí)就可以直接從它的高速緩存讀寫(xiě)數(shù)據(jù),當(dāng)運(yùn)算結(jié)束之后,再將高速緩存中的數(shù)據(jù)刷新到主存當(dāng)中。
  3. 但是當(dāng)出現(xiàn)多核CPU時(shí),每個(gè)核上都存在高速緩存,而且都可能運(yùn)行著線程,線程又是并發(fā)的,它們都會(huì)修改自己所在核的高速緩存中的變量,就會(huì)導(dǎo)致數(shù)據(jù)不一致的情況。

原子性、可見(jiàn)性、有序性

  1. 原子性是指在一個(gè)操作中就是cpu不可以在中途暫停然后再調(diào)度,既不被中斷操作,要不執(zhí)行完成,要不就不執(zhí)行。 -- 處理器優(yōu)化會(huì)導(dǎo)致原子性問(wèn)題
  2. 可見(jiàn)性是指當(dāng)多個(gè)線程訪問(wèn)同一個(gè)變量時(shí),一個(gè)線程修改了這個(gè)變量的值,其他線程能夠立即看得到修改的值。 -- 緩存一致性
  3. 有序性即程序執(zhí)行的順序按照代碼的先后順序執(zhí)行。 -- 指令重排導(dǎo)致有序性問(wèn)題
  4. 如何實(shí)現(xiàn)上面說(shuō)到的三個(gè)特性呢?=>這就是Java的內(nèi)存模型要解決的問(wèn)題了

Java內(nèi)存模型 -- JMM

  1. Java內(nèi)存模型規(guī)定了所有的變量都存儲(chǔ)在主內(nèi)存中,每條線程還有自己的工作內(nèi)存,線程的工作內(nèi)存中保存了該線程中是使到的變量的主內(nèi)存副本拷貝,線程對(duì)變量的所有操作都必須在工作內(nèi)存中進(jìn)行,而不能直接讀寫(xiě)主內(nèi)存。
  2. 不同的線程之間也無(wú)法直接訪問(wèn)對(duì)方工作內(nèi)存中的變量,線程間變量的傳遞均需要自己的工作內(nèi)存和主存之間進(jìn)行數(shù)據(jù)同步進(jìn)行。所以,就可能出現(xiàn)線程1改了某個(gè)變量的值,但是線程2不可見(jiàn)的情況。
  3. 而JMM就作用于工作內(nèi)存和主存之間數(shù)據(jù)同步過(guò)程。他規(guī)定了如何做數(shù)據(jù)同步以及什么時(shí)候做數(shù)據(jù)同步
  4. 所以,JMM就是一種規(guī)范,其用于解決由于多線程通過(guò)共享內(nèi)存進(jìn)行通信時(shí),存在的本地內(nèi)存數(shù)據(jù)不一致、編譯器會(huì)對(duì)代碼指令重排序、處理器會(huì)對(duì)代碼亂序執(zhí)行等帶來(lái)的問(wèn)題。
  5. Java內(nèi)存模型,除了定義了一套規(guī)范,還提供了一系列原語(yǔ)(volatile、synchronized、final、concurrent包),封裝了底層實(shí)現(xiàn)后,供開(kāi)發(fā)者直接使用。

volatile

  1. 使用volatile能將被其修飾的變量在被修改后可以立即同步到主內(nèi)存,被其修飾的變量在每次使用之前都從主內(nèi)存刷新。因此,可以使用volatile來(lái)保證多線程操作時(shí)變量的可見(jiàn)性。
  2. volatile只能保證有序性和可見(jiàn)性。

synchronized

  1. synchronized可以保證原子性、有序性和可見(jiàn)性。
  2. 作用域:
  3. 作用于普通方法
  4. 作用于靜態(tài)方法
  5. 作用于代碼塊
  6. 只有在同步的塊或者方法中才能調(diào)用wait/notify等方法
  7. 作用范圍:
  8. 當(dāng)修飾方法時(shí)候,其只作用于本對(duì)象實(shí)例
  9. 當(dāng)修飾靜態(tài)方法時(shí),作用于該類(lèi)的對(duì)象(靜態(tài)方法本就屬于這個(gè)類(lèi),所以任何關(guān)于該類(lèi)的都可)
  10. 當(dāng)修飾代碼塊時(shí),還是該對(duì)象實(shí)例
  11. 這里再延伸探討一下synchronized是如何保證原子性的呢?這就要先說(shuō)說(shuō)Java虛擬機(jī)如何執(zhí)行線程同步了

Java虛擬機(jī)如何執(zhí)行線程同步

  1. 在Java的內(nèi)存結(jié)構(gòu)中,有兩塊區(qū)域會(huì)被所有的線程共享 -- 方法區(qū)(存儲(chǔ)靜態(tài)變量)和堆(存儲(chǔ)所有對(duì)象實(shí)例),因?yàn)閷?shí)現(xiàn)了共享所以同一個(gè)對(duì)象也應(yīng)該能被多個(gè)線程改變。要實(shí)現(xiàn)這些改變那就需要Java虛擬機(jī)來(lái)管控。
  2. 所以虛擬機(jī)給每個(gè)對(duì)象和類(lèi)都加了一個(gè)鎖,當(dāng)某個(gè)線程要去改變這個(gè)對(duì)象的時(shí)候,就會(huì)去向虛擬機(jī)申請(qǐng)鎖(虛擬機(jī)可能會(huì)延遲給鎖),獲取到鎖的線程對(duì)對(duì)象進(jìn)行修改,之后再將鎖還給虛擬機(jī),虛擬機(jī)又為下個(gè)線程分配鎖。
  3. 其中給類(lèi)加鎖,其實(shí)也是通過(guò)給對(duì)象加鎖實(shí)現(xiàn)的,因?yàn)槊總€(gè)類(lèi)加載的時(shí)候,都會(huì)有一個(gè)java.lang.Class的實(shí)例,給這個(gè)實(shí)例加鎖來(lái)實(shí)現(xiàn)。
  4. 監(jiān)視器(Monitors):JVM中鎖的是通過(guò)一個(gè)叫做監(jiān)視器的東西來(lái)實(shí)現(xiàn)的,監(jiān)視器用來(lái)監(jiān)視一段代碼,保證同一時(shí)間只有一個(gè)線程在執(zhí)行它。
  5. 每一個(gè)監(jiān)視器都與一個(gè)對(duì)段象相關(guān)聯(lián),當(dāng)開(kāi)始運(yùn)行到這段代碼時(shí),線程必須要獲取到它所引用對(duì)象的鎖。一旦獲得鎖,線程便可以進(jìn)入“被保護(hù)”的代碼開(kāi)始執(zhí)行。當(dāng)線程離開(kāi)代碼塊的時(shí)候,無(wú)論如何離開(kāi),都會(huì)釋放所關(guān)聯(lián)對(duì)象的鎖。

synchronized實(shí)現(xiàn)原理

  1. 每個(gè)對(duì)象有一個(gè)監(jiān)視器鎖(monitor)。當(dāng)monitor被占用時(shí)就會(huì)處于鎖定狀態(tài),線程執(zhí)行monitorenter指令時(shí)嘗試獲取monitor的所有權(quán),過(guò)程如下:
    1. 如果monitor的進(jìn)入數(shù)為0,則該線程進(jìn)入monitor,然后將進(jìn)入數(shù)設(shè)置為1,該線程即為monitor的所有者。
    2. 如果線程已經(jīng)占有該monitor,只是重新進(jìn)入,則進(jìn)入monitor的進(jìn)入數(shù)加1。
    3. 如果其他線程已經(jīng)占用了monitor,則該線程進(jìn)入阻塞狀態(tài),直到monitor的進(jìn)入數(shù)為0,再重新嘗試獲取monitor的所有權(quán)。
  2. 執(zhí)行monitorexit的線程必須是objectref所對(duì)應(yīng)的monitor的所有者。
    1. 指令執(zhí)行時(shí),monitor的進(jìn)入數(shù)減1,如果減1后進(jìn)入數(shù)為0,那線程退出monitor,不再是這個(gè)monitor的所有者。其他被這個(gè)monitor阻塞的線程可以嘗試去獲取這個(gè) monitor 的所有權(quán)。
  • 這是synchronized同步代碼塊的原理,還有同步方法是通過(guò)ACC_SYNCHRONIZED標(biāo)志實(shí)現(xiàn)的,具體待補(bǔ)充...

最后回到synchronized是如何保證原子性問(wèn)題上

比如線程1在執(zhí)行monitorenter指令的時(shí)候,會(huì)對(duì)Monitor進(jìn)行加鎖,加鎖后其他線程無(wú)法獲得鎖,除非線程1主動(dòng)解鎖。即使在執(zhí)行過(guò)程中,由于某種原因,比如CPU時(shí)間片用完,線程1放棄了CPU,但是,他并沒(méi)有進(jìn)行解鎖。而由于synchronized的鎖是可重入的,下一個(gè)時(shí)間片還是只能被他自己獲取到,還是會(huì)繼續(xù)執(zhí)行代碼。直到所有代碼執(zhí)行完。這就保證了原子性。

參考文章

how-the-java-virtual-machine-performs-thread-synchronization
再有人問(wèn)你Java內(nèi)存模型是什么,就把這篇文章發(fā)給他。
Synchronized的實(shí)現(xiàn)原理

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容