JMM理解
JMM概念
JMM(Java內(nèi)存模型Java Memory Model,簡稱JMM)本身是一種抽象的概念JMM(Java內(nèi)存模型Java Memory Model,簡稱JMM)本身是一種抽象的概念并不真實(shí)存在,它描述的一組規(guī)則或規(guī)范,通過這組規(guī)范定義了程序中各個(gè)變量(包括實(shí)例字段、靜態(tài)字段何構(gòu)成數(shù)組對(duì)象的元素)的訪問方式。
并不真實(shí)存在,它描述的一組規(guī)則或規(guī)范,通過這組規(guī)范定義了程序中各個(gè)變量(包括實(shí)例字段、靜態(tài)字段何構(gòu)成數(shù)組對(duì)象的元素)的訪問方式。
JMM關(guān)于同步的規(guī)定:
1、線程解鎖前,必須把共享變量的值刷新回主內(nèi)存
2、線程加鎖前,必須讀取主內(nèi)存的最新值到自己的工作內(nèi)存
3、加鎖解鎖是同一把鎖
由于JVM運(yùn)行程序的實(shí)體是線程,而每個(gè)線程創(chuàng)建時(shí)JVM都會(huì)為其創(chuàng)建一個(gè)工作內(nèi)存(有些地方稱為??臻g),工作內(nèi)存是每個(gè)線程的私有數(shù)據(jù)區(qū)域,而Java內(nèi)存模型中的規(guī)定所有變量都存儲(chǔ)在主內(nèi)存,主內(nèi)存是共享內(nèi)存區(qū)域,所有線程都可以訪問,但線程對(duì)變量的操作(讀取賦值等)必須在工作內(nèi)存中進(jìn)行,首先要將變量從主內(nèi)存拷貝到線程自己的工作內(nèi)存空間,然后對(duì)變量進(jìn)行操作,操作完成后再將變量寫會(huì)主內(nèi)存,不能直接操作主內(nèi)存中的變量,各個(gè)線程中的工作內(nèi)存中存儲(chǔ)著主內(nèi)存中的變量副本拷貝,因此不同的線程間無法訪問對(duì)方的工作內(nèi)存,線程間的通信(傳值)必須通過主內(nèi)存來完成,其簡要訪問過程入下圖:
JMM 的八種內(nèi)存交互操作
為了更直觀,先來看看這張圖吧:
- lock(鎖定):作用于主內(nèi)存中的變量,一個(gè)變量在同一時(shí)間只能被一個(gè)線程鎖定,即把變量標(biāo)識(shí)為線程獨(dú)占狀態(tài)。
- read(讀取):作用于主內(nèi)存變量,表示把一個(gè)變量值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存中,以便下一步的 load 操作使用。
- load(載入):作用于線程的工作內(nèi)存的變量,表示把 read 操作從主內(nèi)存中讀取的變量值放到工作內(nèi)存的變量副本中(副本是相對(duì)于主內(nèi)存的變量而言的)。
- use(使用):作用于線程的工作內(nèi)存中的變量,表示把工作內(nèi)存中的一個(gè)變量值傳遞給執(zhí)行引擎,每當(dāng)虛擬機(jī)遇到一個(gè)需要使用變量的值的字節(jié)碼指令時(shí)就會(huì)執(zhí)行該操作。
- assign(賦值):作用于線程的工作內(nèi)存的變量,表示把執(zhí)行引擎返回的值賦值給工作內(nèi)存中的變量,每當(dāng)虛擬機(jī)遇到一個(gè)給變量賦值的字節(jié)碼指令時(shí)就會(huì)執(zhí)行該操作。
- store(存儲(chǔ)):作用于線程的工作內(nèi)存中的變量,把工作內(nèi)存中的一個(gè)變量的值傳遞給主內(nèi)存,以便下一步的 write 操作使用。
- write(寫入):作用于主內(nèi)存的變量,表示把 store 操作從工作內(nèi)存中得到的變量的值放入主內(nèi)存的變量中。
- unlock(解鎖):作用于主內(nèi)存的變量,表示把一個(gè)處于鎖定狀態(tài)的變量釋放出來,釋放后的變量才可以被其他線程鎖定。
JMM 還規(guī)定了以上八種操作需按照如下規(guī)則進(jìn)行:
- 不允許read 和 load、store 和 write 操作之一單獨(dú)出現(xiàn),也就是 read 操作后必須 load,store 操作后必須 write。即不允許一個(gè)變量從主內(nèi)存讀取了但工作內(nèi)存不接受,或者從工作內(nèi)存發(fā)起回寫了但主內(nèi)存不接受的情況出現(xiàn)。
- 不允許線程丟棄它最近的 assign 操作,即變量在工作內(nèi)存中改變了之后必須把該變化同步回主內(nèi)存。
- 不允許線程將沒有 assign 的數(shù)據(jù)從工作內(nèi)存同步到主內(nèi)存。
- 一個(gè)新的變量必須在主內(nèi)存中誕生,不允許工作內(nèi)存直接使用一個(gè)未被初始化的變量。也就是對(duì)變量實(shí)施 use 和 store 操作之前,必須經(jīng)過 load 和 assign 操作。
- 一個(gè)變量同一時(shí)間只能有一個(gè)線程對(duì)其進(jìn)行 lock 操作。但 lock 操作可以被同一條線程重復(fù)執(zhí)行多次,多次 lock 之后,必須執(zhí)行相同次數(shù) unlock 才可以解鎖。
- 如果對(duì)一個(gè)變量進(jìn)行 lock 操作,會(huì)清空所有工作內(nèi)存中此變量的值。在執(zhí)行引擎使用這個(gè)變量前,必須重新 load 或 assign 操作初始化變量的值。
- 如果一個(gè)變量沒有被 lock,就不能對(duì)其進(jìn)行 unlock 操作。也不能 unlock 一個(gè)被其他線程鎖住的變量。
- 一個(gè)線程對(duì)一個(gè)變量進(jìn)行 unlock 操作之前,必須先把此變量同步回主內(nèi)存。
JMM 三大特征
JMM 三大特征分別是:原子性,可見性,有序性。整個(gè) JMM 實(shí)際上也是圍繞著這三個(gè)特征建立起來的,并且也是 Java 并發(fā)編程的基礎(chǔ)。
原子性
原子性是指一個(gè)操作是不可分割、不可中斷的,要么全部執(zhí)行成功要么全部執(zhí)行失敗。
JMM 只能保證對(duì)基本數(shù)據(jù)類型的變量的讀寫操作是原子性的,但 long 和 double 除外(long 和 double 的非原子性協(xié)定)。
我們來看看下面的例子:
int x = 1;
int y = x;
x ++;
上面三行代碼只有第一行是原子性操作,基本類型賦值操作,必定是原子性操作。
第二行代碼先讀取 x 變量的值,再進(jìn)行賦值給 y 變量,進(jìn)行了兩個(gè)操作,不能保證原子性。
第三行代碼先讀取 x 變量的值,再進(jìn)行加 1,最后再賦值給 x 變量,進(jìn)行了三個(gè)操作,不能保證原子性。
在并發(fā)環(huán)境下,為了保證原子性,Java 提供了 synchronized 關(guān)鍵字。因此在 synchronized 修飾的代碼塊之間的操作都是原子性的。
可見性
可見性是指所有線程都能看到共享內(nèi)存的最新狀態(tài)。即當(dāng)一個(gè)線程修改了一個(gè)共享變量的值時(shí),其他線程能夠立即看到該變量的最新值。
對(duì)于可見性問題,Java 是提供了一個(gè) volatile 關(guān)鍵字來保證可見性。當(dāng)一個(gè)共享變量被 volatile 關(guān)鍵字修飾時(shí),這個(gè)變量被修改后會(huì)立即刷新到主內(nèi)存,保證其他線程看到的值一定是最新的。
除了 volatile 關(guān)鍵字之外,final 和 synchronized 也能實(shí)現(xiàn)可見性。
final 關(guān)鍵字修飾的變量,在構(gòu)造器中一旦初始化完成,如果沒有對(duì)象逸出(指對(duì)象沒有初始化完成就可以被別的線程使用),那么其他線程都就可以看見 final 修飾的變量。
synchronized 的原理是,線程進(jìn)入 synchronized 代碼塊后,線程會(huì)獲取到 lock,將會(huì)清空本地內(nèi)存,然后從主內(nèi)存中拷貝共享變量的最新值到本地內(nèi)存作為副本,執(zhí)行代碼,又將修改后的副本值刷新到主內(nèi)存中,最后線程執(zhí)行 unlock。
有序性
有序性是指程序執(zhí)行的順序按照代碼的先后順序執(zhí)行。
在 Java 中,可以通過 volatile 和 synchronized 關(guān)鍵字來保證多線程之間操作的有序性。
volatile 關(guān)鍵字是通過在主存中加入內(nèi)存屏障來達(dá)到禁止指令重排序,來保證有序性。
synchronized 關(guān)鍵字原理是,一個(gè)變量在同一時(shí)刻只能被一個(gè)線程 lock,并且必須 unlock 后,其他線程才可以重新 lock,使得被 synchronized 修飾的代碼塊在多線程之間是串行執(zhí)行的。
【編輯推薦】
【責(zé)任編輯:姜華 TEL:(010)68476606】