JVM并发机制探讨—内存模型、内存可见性和指令重排序
并發(fā)本來(lái)就是個(gè)有意思的問(wèn)題,尤其是現(xiàn)在又流行這么一句話:“高帥富加機(jī)器,窮矮搓搞優(yōu)化”。從這句話可以看到,無(wú)論是高帥富還是窮矮搓都需要深入理解并發(fā)編程,高帥富加多了機(jī)器,需要協(xié)調(diào)多臺(tái)機(jī)器或者多個(gè)CPU對(duì)共享資源的訪問(wèn),因此需要了解并發(fā),窮矮搓搞優(yōu)化需要編寫各種多線程的代碼來(lái)壓榨 CPU的計(jì)算資源,讓它在同一時(shí)刻做更多的事情,這個(gè)更需要了解并發(fā)。
并發(fā)本來(lái)就是個(gè)有意思的問(wèn)題,尤其是現(xiàn)在又流行這么一句話:“高帥富加機(jī)器,窮矮搓搞優(yōu)化”。從這句話可以看到,無(wú)論是高帥富還是窮矮搓都需要深入理解并發(fā)編程,高帥富加多了機(jī)器,需要協(xié)調(diào)多臺(tái)機(jī)器或者多個(gè)CPU對(duì)共享資源的訪問(wèn),因此需要了解并發(fā),窮矮搓搞優(yōu)化需要編寫各種多線程的代碼來(lái)壓榨 CPU的計(jì)算資源,讓它在同一時(shí)刻做更多的事情,這個(gè)更需要了解并發(fā)。
在我前一篇關(guān)于并發(fā)的文章http://my.oschina.net/chihz/blog/54731中提到過(guò)管程,管程的特色是在編程語(yǔ)言中 對(duì)并發(fā)的細(xì)節(jié)進(jìn)行封裝,使程序員可以直接在語(yǔ)言中就得到并發(fā)的支持,而不必自己去處理一些像是控制信號(hào)量之類容易出錯(cuò)且繁瑣的細(xì)節(jié)問(wèn)題。一些語(yǔ)言是通過(guò)在 編譯時(shí)解開(kāi)語(yǔ)法糖的方式去實(shí)現(xiàn)管程,但Java在編譯后生成的字節(jié)碼層面上對(duì)并發(fā)仍然是一層封裝,比如syncrhonized塊在編譯之后只是對(duì)應(yīng)了兩 條指令:monitorenter和monitorexit。更多的并發(fā)細(xì)節(jié)是在JVM運(yùn)行時(shí)去處理的,而不是編譯。這篇文章主要是針對(duì)JVM處理并發(fā)的 一些細(xì)節(jié)的探討。
JAVA內(nèi)存模型
對(duì)于我們平時(shí)開(kāi)發(fā)的業(yè)務(wù)應(yīng)用來(lái)說(shuō),內(nèi)存應(yīng)該是訪問(wèn)速度最快的存儲(chǔ)設(shè)備,對(duì)于頻繁訪問(wèn)的數(shù)據(jù),我們總是習(xí)慣把它們放到內(nèi)存緩存中,有句話不是說(shuō)么,緩 存就像是清涼油,哪里有問(wèn)題就抹一抹。但是CPU的運(yùn)算速度比起內(nèi)存的訪問(wèn)速度還要快幾個(gè)量級(jí),為了平衡這個(gè)差距,于是就專門為CPU引入了高速緩存,頻 繁使用的數(shù)據(jù)放到高速緩存當(dāng)中,CPU在使用這些數(shù)據(jù)進(jìn)行運(yùn)算的時(shí)候就不必再去訪問(wèn)內(nèi)存。但是在多CPU時(shí)代卻有一個(gè)問(wèn)題,每個(gè)CPU都擁有自己的高速緩 存,內(nèi)存又是所有CPU共享的公共資源,于是內(nèi)存此時(shí)就成了一個(gè)臨界區(qū),如果控制不好各個(gè)CPU對(duì)內(nèi)存的并發(fā)訪問(wèn),那么就會(huì)產(chǎn)生錯(cuò)誤,出現(xiàn)數(shù)據(jù)不一致的情 況。為了避免這種情況,需要采取緩存一致性協(xié)議來(lái)保證,這類協(xié)議有很多,各個(gè)硬件平臺(tái)和操作系統(tǒng)的實(shí)現(xiàn)不盡相同。
JVM需要實(shí)現(xiàn)跨平臺(tái)的支持,它需要有一套自己的同步協(xié)議來(lái)屏蔽掉各種底層硬件和操作系統(tǒng)的不同,因此就引入了Java內(nèi)存模型。對(duì)于Java來(lái)說(shuō) 開(kāi)發(fā)者并不需要關(guān)心任何硬件細(xì)節(jié),因此沒(méi)有多核CPU和高速緩存的概念,多核CPU和高速緩存在JVM中對(duì)應(yīng)的是Java語(yǔ)言內(nèi)置的線程和每個(gè)線程所擁有 的獨(dú)立內(nèi)存空間,Java內(nèi)存模型所規(guī)范的也就是數(shù)據(jù)在線程自己的獨(dú)立內(nèi)存空間和JVM共享內(nèi)存之間同步的問(wèn)題。下面這兩張圖說(shuō)明了硬件平臺(tái)和JVM內(nèi)存 模型的相似和差異之處。
Java內(nèi)存模型規(guī)定,對(duì)于多個(gè)線程共享的變量,存儲(chǔ)在主內(nèi)存當(dāng)中,每個(gè)線程都有自己獨(dú)立的工作內(nèi)存,線程只能訪問(wèn)自己的工作內(nèi)存,不可以訪問(wèn)其它 線程的工作內(nèi)存。工作內(nèi)存中保存了主內(nèi)存共享變量的副本,線程要操作這些共享變量,只能通過(guò)操作工作內(nèi)存中的副本來(lái)實(shí)現(xiàn),操作完畢之后再同步回到主內(nèi)存當(dāng) 中。如何保證多個(gè)線程操作主內(nèi)存的數(shù)據(jù)完整性是一個(gè)難題,Java內(nèi)存模型也規(guī)定了工作內(nèi)存與主內(nèi)存之間交互的協(xié)議,首先是定義了8種原子操作:
(1) lock:將主內(nèi)存中的變量鎖定,為一個(gè)線程所獨(dú)占
(2) unclock:將lock加的鎖定解除,此時(shí)其它的線程可以有機(jī)會(huì)訪問(wèn)此變量
(3) read:將主內(nèi)存中的變量值讀到工作內(nèi)存當(dāng)中
(4) load:將read讀取的值保存到工作內(nèi)存中的變量副本中。
(5) use:將值傳遞給線程的代碼執(zhí)行引擎
(6) assign:將執(zhí)行引擎處理返回的值重新賦值給變量副本
(7) store:將變量副本的值存儲(chǔ)到主內(nèi)存中。
(8) write:將store存儲(chǔ)的值寫入到主內(nèi)存的共享變量當(dāng)中。
我們可以看到,要保證數(shù)據(jù)的同步,lock和unlock定義了一個(gè)線程訪問(wèn)一次共享內(nèi)存的界限,有l(wèi)ock操作也必須有unlock操作,另外一 些操作也必須要成對(duì)出現(xiàn)才可以,像是read和load、store和write需要成對(duì)出現(xiàn),如果單一指令出現(xiàn),那么就會(huì)造成數(shù)據(jù)不一致的問(wèn)題。 Java內(nèi)存模型也針對(duì)這些操作指定了必須滿足的規(guī)則:
(1) read和load、store和write必須要成對(duì)出現(xiàn),不允許單一的操作,否則會(huì)造成從主內(nèi)存讀取的值,工作內(nèi)存不接受或者工作內(nèi)存發(fā)起的寫入操作而主內(nèi)存無(wú)法接受的現(xiàn)象。
(2) 在線程中使用了assign操作改變了變量副本,那么就必須把這個(gè)副本通過(guò)store-write同步回主內(nèi)存中。如果線程中沒(méi)有發(fā)生assign操作,那么也不允許使用store-write同步到主內(nèi)存。
(3) 在對(duì)一個(gè)變量實(shí)行use和store操作之前,必須實(shí)行過(guò)load和assign操作。
(4) 變量在同一時(shí)刻只允許一個(gè)線程對(duì)其進(jìn)行l(wèi)ock,有多少次lock操作,就必須有多少次unlock操作。在lock操作之后會(huì)清空此變量在工作內(nèi)存中原 先的副本,需要再次從主內(nèi)存read-load新的值。在執(zhí)行unlock操作前,需要把改變的副本同步回主存。
轉(zhuǎn)載于:https://blog.51cto.com/ibin1520/1350561
總結(jié)
以上是生活随笔為你收集整理的JVM并发机制探讨—内存模型、内存可见性和指令重排序的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Windows Management F
- 下一篇: JDK源码笔记-java.util.Ha