轉(zhuǎn)載:http://www.cnblogs.com/leesf456/p/5291484.html
Java虛擬機(jī)規(guī)范中試圖定義一種Java內(nèi)存模型(Java Memory Model, JMM)來(lái)屏蔽掉各種硬件和操作系統(tǒng)的內(nèi)存訪問(wèn)差異,以實(shí)現(xiàn)讓Java程序在各種平臺(tái)下都能達(dá)到一致的內(nèi)存訪問(wèn)效果。
1、Java并發(fā)基礎(chǔ)
在并發(fā)編程中存在兩個(gè)關(guān)鍵問(wèn)題:(1) 線程之間如何通信;(2)線程之間如何同步。
1.1 通信
通信是指線程之間以何種機(jī)制來(lái)交換信息。在命令式編程中,線程之間的通信機(jī)制有兩種:共享內(nèi)存 和 消息傳遞。
共享內(nèi)存:在共享內(nèi)存的并發(fā)模型里,線程之間共享程序的公共狀態(tài),線程之間通過(guò)寫-讀內(nèi)存中的公共狀態(tài)來(lái)隱式進(jìn)行通信。
消息傳遞:在消息傳遞的并發(fā)模型里,線程之間沒(méi)有公共狀態(tài),線程之間必須通過(guò)明確的發(fā)送消息來(lái)顯式進(jìn)行通信。
1.2 同步
同步是指程序用于控制不同線程之間操作發(fā)生相對(duì)順序的機(jī)制。
在共享內(nèi)存并發(fā)模型里,同步是顯式進(jìn)行的。程序員必須顯式指定某個(gè)方法或某段代碼需要在線程之間互斥訪問(wèn)。
在消息傳遞的并發(fā)模型里,由于消息的發(fā)送必須在消息的接收之前, 因此同步是隱式進(jìn)行的。
Java并發(fā)采用的是共享內(nèi)存模型,通信隱式進(jìn)行;同步顯示指定。
Java同步的三種方式:(1)volatile、(2)鎖、(3)final
2、Java內(nèi)存模型
Java內(nèi)存模型JMM(Java Memory Model)主要目標(biāo)是定義程序中各個(gè)變量(非線程私有)的訪問(wèn)規(guī)則,即在虛擬機(jī)中將變量存儲(chǔ)到內(nèi)存和從內(nèi)存取出變量這樣的底層細(xì)節(jié)。
Java中每個(gè)線程都有自己私有的工作內(nèi)存。工作內(nèi)存保存了被該線程使用的變量的主內(nèi)存副本拷貝,線程對(duì)變量的讀寫操作都必須在自己的工作內(nèi)存中進(jìn)行,無(wú)法直接讀寫主內(nèi)存中的變量。兩個(gè)線程無(wú)法直接訪問(wèn)對(duì)方的工作內(nèi)存。
2.1 線程、工作內(nèi)存、主內(nèi)存關(guān)系
理解線程、工作內(nèi)存、主內(nèi)存之間的關(guān)系時(shí),我們可以類比物理機(jī)中CPU、高速緩存、內(nèi)存之間關(guān)系。
CPU、高速緩存、主內(nèi)存之間的關(guān)系如下:
線程、工作內(nèi)存、主內(nèi)存的關(guān)系如下:
若線程A要與線程B通信(訪問(wèn)變量)。首先,線程A把工作內(nèi)存中的共享變量的值刷新到主內(nèi)存中;然后,線程B從主內(nèi)存中讀取更新過(guò)的變量的值到自己的工作內(nèi)存中。
2.2 內(nèi)存間通信的指令
內(nèi)存間通信,主要指線程私有的工作內(nèi)存與主內(nèi)存之間的通信,如線程間共享變量的傳遞。主要有如下操作。
read、load操作;store、write操作必須按順序執(zhí)行(并非連續(xù)執(zhí)行)。
上述的8個(gè)操作需要滿足如下規(guī)則:
2.3 重排序
在執(zhí)行程序時(shí)為了提高性能,編譯器和處理器常常會(huì)對(duì)指令做重排序。重排序會(huì)遵守?cái)?shù)據(jù)的依賴性,編譯器和處理器不會(huì)改變存在數(shù)據(jù)依賴關(guān)系的兩個(gè)操作的執(zhí)行順序。重排序分為如下三種類型。
編譯器優(yōu)化的重排序。編譯器在不改變單線程程序語(yǔ)義的前提下,可以重新安排語(yǔ)句的執(zhí)行順序。
指令級(jí)并行的重排序。現(xiàn)代處理器采用了指令級(jí)并行技術(shù)(Instruction-Level Parallelism, ILP)來(lái)將多條指令重疊執(zhí)行。如果不存在數(shù)據(jù)依賴性,處理器可以改變語(yǔ)句對(duì)應(yīng)機(jī)器指令的執(zhí)行順序。
內(nèi)存系統(tǒng)的重排序。由于處理器使用緩存和讀/寫緩沖區(qū),這使得加載和存儲(chǔ)操作看上去可能是在亂序執(zhí)行。
而我們編寫的Java源程序中的語(yǔ)句順序并不對(duì)應(yīng)指令中的相應(yīng)順序,如(int a = 0; int b = 0;翻譯成機(jī)器指令后并不能保證a = 0操作在b = 0操作之前)。因?yàn)榫幾g器、處理器會(huì)對(duì)指令進(jìn)行重排序,通常而言,Java源程序變成最后的機(jī)器執(zhí)行指令會(huì)經(jīng)過(guò)如下的重排序。
2.4 內(nèi)存屏障指令
為了保證內(nèi)存可見(jiàn)性,Java編譯器在生成指令序列的適當(dāng)位置會(huì)插入內(nèi)存屏障指令來(lái)禁止特定類型的處理器重排序。內(nèi)存屏障指令分為如下四種:
2.5 先行發(fā)生原則(happens-before)
先行發(fā)生原則是判斷數(shù)據(jù)是否存在競(jìng)爭(zhēng)、線程是否安全的主要依據(jù)。
如果一個(gè)操作執(zhí)行的結(jié)果需要對(duì)另一個(gè)操作可見(jiàn),那么這兩個(gè)操作之間必須要存在 happens-before 關(guān)系,如果操作A先行發(fā)生與操作B,即A操作產(chǎn)生的結(jié)果能夠被操作B觀察到。
具體的 happens-before 原則如下:
程序順序規(guī)則:在一個(gè)線程內(nèi),按照程序代碼順序,書寫在前面的操作先行發(fā)生于書寫在后面的操作(控制流操作而不是程序代碼順序)。
管程鎖定規(guī)則:一個(gè)unlock操作先行發(fā)生于后面對(duì)同一個(gè)鎖的lock操作。
volatile變量規(guī)則:對(duì)一個(gè) volatile變量的寫操作,先行發(fā)生于后面對(duì)這個(gè)變量的讀操作。
線程啟動(dòng)規(guī)則:Thread對(duì)象的start()方法先行發(fā)生于此線程的每一個(gè)動(dòng)作。
線程終止規(guī)則:線程中所有操作都先行發(fā)生于對(duì)此線程的終止檢測(cè)。
線程中斷規(guī)則:對(duì)線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測(cè)到中斷事件的發(fā)生。
對(duì)象終結(jié)規(guī)則:一個(gè)對(duì)象的初始化完成(構(gòu)造函數(shù)執(zhí)行結(jié)束)先行發(fā)生于它的finalize()方法的開(kāi)始。
傳遞性:如果操作A先行發(fā)生于操作B,且操作B先行發(fā)生于操作C,那么操作A先行發(fā)生于操作C。
說(shuō)明:happens-before 僅僅要求前一個(gè)操作(執(zhí)行的結(jié)果)對(duì)后一個(gè)操作可見(jiàn),且前一個(gè)操作按順序排在第二個(gè)操作之前(可能會(huì)發(fā)生指令重排)。時(shí)間先后順序與happens - before原則之間沒(méi)有太大的關(guān)系。
2.6 as-if-serial 語(yǔ)義
as-if-serial 的語(yǔ)義是:
不管怎么重排序(編譯器和處理器為了提高并行度),(單線程)程序的執(zhí)行結(jié)果不能被改變。編譯器,runtime 和處理器都必須遵守 as-if-serial 語(yǔ)義。為了遵守 as-if-serial 語(yǔ)義,編譯器和處理器不會(huì)對(duì)存在數(shù)據(jù)依賴關(guān)系的操作做重排序,因?yàn)檫@種重排序會(huì)改變執(zhí)行結(jié)果。但是,如果操作之間不存在數(shù)據(jù)依賴關(guān)系,這些操作就可能被編譯器和處理器重排序。