JMM內存模型詳解(一)

本文開始死磕JMM(Java內存模型)由於知識點較多,分來寫

該文爲JMM第一篇

技術往往是枯燥的,本文文字較多

1. JMM是什麼?

其實JMM很好理解,我簡單的解釋一下,在Java多線程中我們經常會涉及到兩個概念就是線程之間是如何通信和線程之間的同步,那什麼是線程之間的通信呢,其實就是兩個線程之間互相交換信息線程之間通信的方式共有兩種:一種就是共享內存,和消息傳遞。在共享內存中的併發模型中線程是通過讀取主內存的共享信息來進行隱性通信的。在消息傳遞通信中線程之間沒有公共的狀態,只能通過發送消息來進行顯性通信。然而這只是線程通信,那麼同步呢,同步就是在多線程的情況下有順序的去執行。在共享內存中同步時顯式進行的,在代碼中我們必須要去指定方法需要同步執行比如說加同步鎖等。在消息傳遞的併發模型中發送消息必須是在消接收之前,所以同步時隱式的。

2.爲什麼要涉及到線程併發通信

java內存模型其實可以說是Java併發內存模型,在Java中是採用的共享內存模型的方式,所以Java線程之間的通信是隱式進行的,對我們是完全透明的,如果你不瞭解通信機制的話會產生各種線程可見性的問題。其實在Java中所有的靜態域,域和數組元素都存在堆內存中,堆內存在線程中是共享的一般我們都稱之爲共享變量,局部變量,方法定義參數和異常處理參數不會在線程中共享,所以不會存在線程可見性的問題。上面我就說過線程之間的通信是由JMM來進行控制的,JMM來決定了一個線程操作了共享變量後如何對另一個線程可見。從上面所說的概念來看的話,JMM定義了線程與主內存的關係。

3.JMM規定

其實這樣做的原因就是Java是跨平臺語言,在個操作系統中內存都有一定的差異性,這樣久造成了併發不一致,所以JMM的作用就是用來屏蔽掉不同操作系統中的內存差異性來保持併發的一致性。同時JMM也規範了JVM如何與計算機內存進行交互。簡單的來說JMM就是Java自己的一套協議來屏蔽掉各種硬件和操作系統的內存訪問差異,實現平臺一致性達到最終的"一次編寫,到處運行",說了這麼多,JMM到底是怎麼控制的呢?然後如何通信的呢?我們繼續往下看。

4.模型

JMM是一個抽象的概念,並不是真實的存在,它涵蓋了緩衝區,寄存器以及其他硬件和編譯器優化。

Java內存模型抽象圖如下:


從上圖可以看出每個線程都有一個本地內存,如果線程想要通信的話要執行一下步驟:

  • A線程先把本地內存的值寫入主內存
  • B線程從主內存中去讀取出A線程寫的值

再看下面的這個圖,表示了A如何向B發送消息

假設這時候有一個共享變量X默認值都是爲0,那麼線程A把X的值修改爲1,這時候如何才能同步到B線程呢。

如果A線程把X修改成1之後,A線程會把X從A的本地內存中寫入到主內存中,這樣的話主內存的X就等於1了,這時候B線程就會去讀取主內存的X變量,存入B的本地內存中,這樣B線程的X變量值也就會變成了1。這樣對嗎。那現在如何通信我是知道了關鍵它究竟是如何來實現的,就是如何來實現通信的呢?

5.通信

上面所說的步驟其實就是實現了線程之間的通信,但是不要以爲線程之間的通信就是這麼簡單的,其實在Java中JMM內存模型定義了八種操作來實現同步的細節。

  • read 讀取,作用於主內存把變量從主內存中讀取到本本地內存。
  • load 加載,主要作用本地內存,把從主內存中讀取的變量加載到本地內存的變量副本中
  • use 使用,主要作用本地內存,把工作內存中的一個變量值傳遞給執行引擎,每當虛擬機遇到一個需要使用變量的值的字節碼指令時將會執行這個操作。、
  • assign 賦值 作用於工作內存的變量,它把一個從執行引擎接收到的值賦值給工作內存的變量,每當虛擬機遇到一個給變量賦值的字節碼指令時執行這個操作。
  • store 存儲 作用於工作內存的變量,把工作內存中的一個變量的值傳送到主內存中,以便隨後的write的操作。
  • write 寫入 作用於主內存的變量,它把store操作從工作內存中一個變量的值傳送到主內存的變量中。
  • lock 鎖定 :作用於主內存的變量,把一個變量標識爲一條線程獨佔狀態。
  • unlock 解鎖:作用於主內存變量,把一個處於鎖定狀態的變量釋放出來,釋放後的變量纔可以被其他線程鎖定。

所以看似簡單的通信其實是這八種狀態來實現的。

同時在Java內存模型中明確規定了要執行這些操作需要滿足以下規則:

  • 不允許read和load、store和write的操作單獨出現。
  • 不允許一個線程丟棄它的最近assign的操作,即變量在工作內存中改變了之後必須同步到主內存中。
  • 不允許一個線程無原因地(沒有發生過任何assign操作)把數據從工作內存同步回主內存中。
  • 一個新的變量只能在主內存中誕生,不允許在工作內存中直接使用一個未被初始化(load或assign)的變量。即就是對一個變量實施use和store操作之前,必須先執行過了assign和load操作。
  • 一個變量在同一時刻只允許一條線程對其進行lock操作,lock和unlock必須成對出現
  • 如果對一個變量執行lock操作,將會清空工作內存中此變量的值,在執行引擎使用這個變量前需要重新執行load或assign操作初始化變量的值
  • 如果一個變量事先沒有被lock操作鎖定,則不允許對它執行unlock操作;也不允許去unlock一個被其他線程鎖定的變量。
  • 對一個變量執行unlock操作之前,必須先把此變量同步到主內存中(執行store和write操作)。

所以上面說的操作要嚴格執行。

目前寫了這麼多,下文預告:

LinkedHashMap源碼分析

參考資料《深入Java內存模型》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章