劍指Offer(鎖)——JMM的內存模型

Java內存模型(Java Memory Model, 簡稱JMM)本身是一種抽象的概念並不是真實存在的,描述的是一種規則或規範,通過這組規範定義了程序中各個變量(包括實例字段,靜態字段和構成數組對象的元素)的訪問方式。
在這裏插入圖片描述
簡單說明一下:

  1. 線程內的變量都是存儲在棧中的,這些變量被稱作線程的私有變量,是存在線程的工作內存中;
  2. JMM要求所有變量的信息,需要放進主內存,線程之間是通過主內存區進行通信的;
  3. 線程無法直接操作主內存,只能通過操作線程操作工作內存,然後寫進主內存進行線程交流,線程內部變量都是主內存的副本。

JMM的主內存:

  1. JMM主內存是用來存儲所有的Java實例對象的;
  2. 包括成員變量、類信息、常量、靜態變量等等;
  3. 由於是數據共享的區域,有可能發生線程安全的問題。

線程內部的工作內存:

  1. 存儲當前方法的所有本地變量信息,本地變量對其他線程不可見;
  2. 字節碼行號指示器,Native方法信息;
  3. 屬於線程私有數據區域,不存在線程安全問題。

JMM和Java內存區域劃分是不同的概念層次,JMM描述的是一組規則圍繞原子性,有序性和可見性展開。但都是存在共享區域和私有區域的。

總結主內存和工作內存的數據存儲類型和操作方式:

  1. 方法內的基本數據類型本地變量將直接存儲在工作內存的棧幀結構中;
  2. 引用類型的本地變量會將引用存儲在工作內存,實例存儲在主內存中;
  3. 成員變量、static、類信息存儲在主內存中;
  4. 主內存共享的方式是線程各拷貝一份數據到工作內存中,操作完成刷新回到主內存中。

JMM如何解決可加性的問題的???
在這裏插入圖片描述
一般修改一個變量都是針對緩存去修改的這一個瞬間緩存是不會自動同步到主內存的,會導致另外一個核的線程無法同步這個修改信息,無法滿足一致性。

引入多線程之後,存在很高的數據依賴性,不論編譯器和處理器如何重排序都必須尊重數據依賴性的要求,否則破壞了數據的正確性,這是JMM存在的問題。

爲了提升性能,編譯器經常會使用指令重排序,要求如下:

  1. 單線程環境下不能改變程序運行的結果;
  2. 存在數據依賴關係的不允許重排序。

只有通過Happens-Before原則推出來的才能夠進行指令的重排序。

Happens-Before原則:

  1. 程序次序規則:一個線程內,按照代碼順序,書寫前面的操作先行發生於書寫在後面的操作;
  2. 鎖定規則:一個unlock操作先行發生於後面對同一個鎖的lock操作;
  3. volatile變量規則:對一個變量的寫操作先行發生於後面對這個變量的讀操作;
  4. 傳遞規則:如果操作A先行發生於操作B,而操作B又先行發生於操作C,則可以得出操作A先行發生於操作C;
  5. 線程啓動規則:Thread對象的start方法先行發生於此線程的任何一個動作;
  6. 線程中斷規則:對線程interrupt()方法的調用先行發生於被中斷線程的代碼檢測到中斷事件的發生;
  7. 線程終結規則:線程中所有的操作都先行發生於線程的終止檢測,我們可以通過Thread.join()方法結束、Thread.isAlive()的返回值手段檢測到線程已經終止執行;
  8. 對象終結規則:一個對象的初始化完成先行發生於她的finalize()方法的開始。

如果操作不滿足上面的規則,這個操作沒有順序執行的保證,JMM會對這個操作進行重排序,反之如果A Happens-Before操作B,那麼操作A在內存上的操作對操作B都是可見的。。

volatile:JVM提供的輕量級同步機制

被volatile修飾的共享變量對所有線程總是可見的,就是當一個線程修改了被volatile修飾的變量,另外一個線程就會立刻知道這個變量的修改情況。

volatile如何保證立刻可見的???

寫一個volatile變量時候JMM會把該線程對應的工作內存中的共享變量立刻刷新到主內存中。
讀取到一個volatile變量時候JMM把該線程對應的工作內存置爲無效。

volatile如何禁止重排優化的???

需要先了解一個概念——內存屏障(Memory Barrier)

  1. 保證特定操作的執行順序;
  2. 保證某些變量的內存可見性。

通過插入內存屏障來禁止在內存屏障前後的指令執行重排序優化,強制性的刷新出CPU的緩存數據,因此任何CPU上的線程都能讀取到這些數據的最新版本。

volatile和synchronized作用相同,那麼區別又在哪裏呢???

  1. volatile本質是在告訴JVM當前變量在寄存器(工作內存)中的值是不確定的,需要從主存中進行讀取;synchronized則是鎖定當前變量只有當前線程可以訪問該變量,其他線程被阻塞直到該線程完成變量操作爲止;
  2. volatile僅能使用在變量級別;synchronized則可以使用在變量、方法和類級別;
  3. volatile僅能實現變量的修改可見性,不能保證原子性;synchronized則可以保證變量修改的可見性和原子性;
  4. volatile不會造成線程的阻塞;synchronized可能會造成線程的阻塞;
  5. volatile標記的變量不會被編譯器優化;synchronized標記的變量可以被編譯器優化。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章