Java並發編程之Java內存模型

1、什麼是Java的內存模型

Java內存模型簡稱JMM(Java Memory Model),JMM是和多線程並發相關的一組規范。各個jvm實現都要遵循這個JMM規范。才能保證Java代碼在不同虛擬機順利運行。因此,JMM 與處理器、緩存、並發、編譯器有關。它解決瞭CPU 多級緩存、處理器優化、指令重排等導致的結果不可預期的問題。

在這裡插入圖片描述

2、為什麼需要Java內存模型

程序的運行結果依賴於處理器,而不同的處理器規則都不一樣,不同處理器差異是很大的,所以同段代碼在處理器A運行正常,搬到處理器B運行結果是不一樣的,所以為瞭兼容這種差異,推出瞭Java內存模型規范,JMM是一個規范標準,JMM保證瞭不同處理器的處理結果一致,同時也保證不同編譯器、jvm等等的一致性。所以就保證瞭Java語言“書寫一次、到處運行”

3、Java內存模型及操作規范

1.共享變量都是放在主內存中的

2.每個線程都有自己的工作內存,線程隻可操作自己的工作內存

3.線程要操作共享變量,需要從主內存中讀取到工作內存,改變值之後要從工作內存同步到主內存

在這裡插入圖片描述

4、Java內存模型規定的原子操作

Java內存模型的同步交換協議,規定瞭8種原子操作

原子操作:不可被中斷的一個或一系列操作

  • lock(鎖定):將主內存中的變量鎖定,為一個線程所獨占
  • unlock(解鎖):將lock加的鎖解除,其他的線程有機會訪問此變量
  • read(讀取):作用於主內存變量,將主內存中的變量值讀取到工作內存
  • load(加載):作用於工作內存,將read讀取到的值保存到工作內存中的變量副本
  • use(使用):作用於工作內存變量,將值傳遞給線程的代碼執行引擎
  • assign(賦值):作用於工作內存變量,將執行引擎處理返回的值重新賦值給變量副本
  • store(存儲):作用於工作內存變量,將變量副本的值傳送到主內存中
  • write(寫入):作用於主內存變量,將store傳送過來的值寫入到主內存的共享變量中

Java內存模型的同步交互協議,執行上述8種原子操作時必須滿足如下規則

不允許read和load,store和write操作之一單獨出現。即不允許加載或同步工作到一半。

不允許一個線程丟棄它最近的assign操作,即變量在工作內存中改變之後,必須將數據同步回主內存

不允許一個線程無原因地(無assign操作)將數據從工作內存同步到主內存中。

一個新的變量可能在主內存中誕生。

一個變量在同一個時刻隻允許一條線程對其進行lock操作,但lock操作可以被同一條線程重復執行多次,多次lock之後必須要執行相同次數unlock操作,變量才會解鎖

如果對一個對象進行lock操作,那麼會清空工作內存變量中的值,在執行引擎使用這個變量前,需要重新執行load或assign操作初始變量的值

如果一個對象事先沒有被lock,就不允許對其進行unlock操作,也不允許去unlock一個被其他線程鎖住的變量。

對一個變量執行unlock操作之前,必須將此變量同步回主內存中(執行store、write)

5、Java內存模型同步協議

Java內存模型的同步協議,操作規范 將一個變量從主內存復制到工作內存要順序執行read、load操作;要將變量從工作內存同步回主內存要用store、write操作。隻要求順序執行,不一定是連續執行

圖引用網上資料:

在這裡插入圖片描述

6、Java內存模型的HB法則

並發編程有三個重要特效:原子行可見性有序性

  • 原子性:原子性是指一個或者多個操作,要麼全部執行且執行過程不會被其它操作打斷,要麼全部不執行。
  • 可見性:可見性是指共享變量對於多個線程都是可見的,也即一個線程修改瞭變量,其它線程馬上就能知道
  • 有序性:有序性是指程序的執行順序按照代碼的先後順便執行

在說JMM的happens-before(HB)法則之前,先說說並發編程的有序性。說到並發線程的有序性,還需要涉及到指令重排序

  • 什麼是指令重排?

假如我們寫一個程序,我們會期待這些語句的實際執行順便和代碼的順序是一致的,大部分情況是一致的,但實際上,編譯器、JVM 或者 CPU 都有可能出於優化等目的,對執行的順序進行調整,這個就是指令重排序

  • 重排序的好處:提高處理速度

代碼順序如圖:

在這裡插入圖片描述

指令重排後,a=100; a= a+100會提到一起執行,效率提高

在這裡插入圖片描述

上面的例子,是可以提高執行效率,但是有時候指令重排是會導致問題的,如下代碼例子,代碼順序是先初始化content,然後設置標識為true,線程B檢測到為true之後,調用content的方法

在這裡插入圖片描述

如果指令重排後,這種情況就會出現沒初始化完成,就直接調用conten的方法

在這裡插入圖片描述

所以,指令重排有好處也有壞處,一般可能是cpu、編譯器或者是內存會進行指令重排,為瞭避免指令重排,保證並發編程的有序性,有時候需要使用synchronized等鎖或者volatile等等方式避免

1.JMM規定瞭happens-before(先行發生)原則,來保證很多操作的有序性。

2.當我們代碼操作不滿足先行發生原則時,則需在編碼時使用volatile、synchronized來保證有序性

JMM的HB法則

  • 程序順序規則:每個線程的每個操作都happens-before該線程中任意的後續操作
  • 監視器鎖規則:一個鎖的解除,happens-before於隨後對這個鎖的加鎖
  • volatile變量規則:對volatile域的寫,happens-before於任意後續對這個volatile域的讀
  • 線程啟動規則:在某個線程對象上調用start()方法happens-before被啟動線程中的任意動作
  • 線程終止規則:線程中所有操作都先行發生於對此線程的終止檢測,如在線程t1中成功執行瞭t2.join(),則t2中的所有操作對t2可見
  • 線程中斷規則:對線程interrupt()方法的調用先行發生於被中斷線程的代碼檢測到中斷事件的發生
  • 對象終結規則:一個對象的初始化完成(構造函數執行結束)先行發生於它的finalize方法的開始傳
  • 遞性:如果A happens-before於B,且B happens-before 於C,那麼A happens-before於C

總結

本篇文章就到這裡瞭,希望能夠給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!

推薦閱讀: