1. 程式人生 > >Java多線程內存模型

Java多線程內存模型

原因 https img 線程 變量同步 共享變量 代碼執行 寫到 ext


JMM的基本概念

Java作為平臺無關性語言,JLS(Java語言規範)定義了一個統一的內存管理模型JMM(Java Memory Model)。JMM規定了jvm內存分為主內存和工作內存 ,主內存存放程序中所有的類實例、靜態數據等變量,是多個線程共享的,而工作內存存放的是該線程從主內存中拷貝過來的變量以及訪問方法所取得的局部變量,是每個線程私有的其他線程不能訪問。每個線程對變量的操作都是以先從主內存將其拷貝到工作內存再對其進行操作的方式進行,多個線程之間不能直接互相傳遞數據通信,只能通過共享變量來進行。
2
從上圖來看,線程1與線程2之間如要通信的話,必須要經歷下面2個步驟:

首先,線程1把本地工作內存中更新過的共享變量刷新到主內存中去。
然後,線程2到主內存中去讀取線程1之前已更新過的共享變量。
典型的高並發引起的問題就存在由於線程讀取到的數據還沒有從另外的線程刷新到主內存中而引起的數據不一致問題。


主內存與工作內存的數據交互

JLS一共定義了8種操作來完成主內存與線程工作內存的數據交互:

lock:把主內存變量標識為一條線程獨占,此時不允許其他線程對此變量進行讀寫
unlock:解鎖一個主內存變量
read:把一個主內存變量值讀入到線程的工作內存
load:把read到變量值保存到線程工作內存中作為變量副本
use:線程執行期間,把工作內存中的變量值傳給字節碼執行引擎
assign:字節碼執行引擎把運算結果傳回工作內存,賦值給工作內存中的結果變量
store:把工作內存中的變量值傳送到主內存
write:把store傳送進來的變量值寫入主內存的變量中

使用標準的操作再來重現一下上方的2個線程之間的交互流程則是這樣的:

線程1從主內存read一個值為0的變量x到工作內存
使用load把變量x保存到工作內存作為變量副本
將變量副本x使用use傳遞給字節碼執行引擎進行x++操作
字節碼執行引擎操作完畢後使用assign將結果賦值給變量副本
使用store把變量副本傳送到主內存
使用write把store傳送的數據寫到主內存
線程2從主內存read到x,然後load–>use–>assign–>store–>write

另外使用這8種操作也有一些規則:
read 和 load必須以組合的方式出現,不允許一個變量從主內存讀取了但工作內存不接受情況出現
store和write必須以組合的方式出現,不允許從工作內存發起了存儲操作但主內存不接受的情況出現

工作內存的變量如果沒有經過 assign 操作,不允許將此變量同步到主內存中
在 use 操作之前,必須經過 load 操作
在 store 操作之前,必須經過 assign 操作
unlock 操作只能作用於被 lock 操作鎖定的變量
一個變量被執行了多少次 lock 操作就要執行多少次 unlock 才能解鎖
一個變量只能在同一時刻被一條線程進行 lock 操作
執行 lock 操作後,工作內存的變量的值會被清空,需要重新執行 load 或 assign 操作初始化變量的值
對一個變量執行 unlock 操作之前,必須先把此變量同步回主內存中


多線程中的原子性、可見性、有序性

原子性:關於原子性的定義可以參考我的上篇博客《淺談數據庫事務》。在JLS中保證原子性的操作包括read、load、assign、use、store和write。基本數據類型(除了long 和double)操作都具有原子性。
如果需要更大範圍的原子性操作的時候,可以使用lock和unlock操作來完成這種需求。
可見性:是指當一個線程修改了共享變量的值,其他線程是否能夠立即得知這個修改。
由上方JMM的概念得知,線程操作數據是在工作內存的,當多個線程操作同一個數據的時候很容易讀取到還沒有被write到主內存變量的值。
Java是如何保證可見性的:volatile、synchronized、final關鍵字
有序性:在並發時,程序的執行可能會出現亂序。給人的直觀感覺就是:寫在前面的代碼,會在後面執行。有序性問題的原因是因為程序在執行時,可能會進行指令重排,重排後的指令與原指令的順序未必一致。關於指令重排會在下方講。


指令重排

int a=1;
int b=2;
int c=3;
int d=4;

你能說出上方這段代碼的執行順序麽?其實我們可能理所當然的以為它會從上往下順序執行。事實上,在實際運行時,為了優化指令的執行順序等,代碼指令可能並不是嚴格按照代碼語句順序執行的。上方的代碼執行順序可能完全反過來,這個就是指令重排。
不過呢,指令重排也不是可以隨意重排的,它需要遵守一定的規則:
程序順序規則:一個線程內保證語義的正確性。
鎖規則:解鎖肯定先於隨後的加鎖前。
volatile規則:對一個volatile的寫,先於volatile的讀。
傳遞性:如果A 先於 B,且B 先於 C,那麽A 肯定先於 C。
start()規則:線程的start()操作先於線程的其他操作。
join()規則:線程的所有操作先於線程的關閉。
程序中斷規則:線程的中斷先於被中斷後執行的代碼。
對象finalize規則:一個對象的初始化完成先於finalize()方法。


volatile關鍵字

volatile關鍵字旨在告訴虛擬機在這個地方要註意不能隨意的進行指令重排,而虛擬機看到一個變量被volatile修飾以後就會采用一些特殊的手段來保證變量的可見性。不過要註意的是volatile關鍵字不能保證原子性。
技術分享圖片

Java多線程內存模型