Java內(nèi)存模型(Java Memory Model ,JMM)就是一種符合內(nèi)存模型規(guī)范的,屏蔽了各種硬件和操作系統(tǒng)的訪問(wèn)差異的订歪,保證了Java程序在各種平臺(tái)下對(duì)內(nèi)存的訪問(wèn)都能保證效果一致的機(jī)制及規(guī)范。
一、內(nèi)存屏障
在CPU中,每個(gè)CPU又有多級(jí)緩存己儒,一般分為L(zhǎng)1,L2捆毫,L3闪湾。
CPU要讀取一個(gè)數(shù)據(jù)時(shí),首先從一級(jí)緩存中查找绩卤,如果沒(méi)有找到再?gòu)亩?jí)緩存中查找途样,如果還是沒(méi)有就從三級(jí)緩存或內(nèi)存中查找,每個(gè)cpu有且只有一套自己的緩存濒憋。
因?yàn)檫@些緩存何暇,提高了數(shù)據(jù)訪問(wèn)性能,避免每次都向主內(nèi)存索取跋炕,但是弊端也很明顯赖晶,不能實(shí)時(shí)的和內(nèi)存發(fā)生信息交換,涉及到一個(gè)緩存一致性的問(wèn)題辐烂,而且由于操作系統(tǒng)可能存在重排序遏插,導(dǎo)致讀取到錯(cuò)誤的數(shù)據(jù),因此纠修,操作系統(tǒng)提供了一些內(nèi)存屏障以解決這種問(wèn)題.
不同硬件對(duì)內(nèi)存屏障的實(shí)現(xiàn)方式不一樣胳嘲,java屏蔽掉這些差異,通過(guò)jvm生成內(nèi)存屏障
1. 內(nèi)存屏障的作用
cpu執(zhí)行指令可能是無(wú)序的扣草,它有兩個(gè)比較重要的作用
- 阻止屏障兩側(cè)指令重排序了牛。
- 強(qiáng)制把寫緩沖區(qū)/高速緩存中的臟數(shù)據(jù)等寫回主內(nèi)存,讓緩存中相應(yīng)的數(shù)據(jù)失效辰妙。
2. volatile關(guān)鍵字
volatile 保證了內(nèi)存可見(jiàn)性并且禁止CPU指令重排序鹰祸。
可見(jiàn)性:指線程之間的可見(jiàn)性,一個(gè)線程修改的狀態(tài)對(duì)另一個(gè)線程是可見(jiàn)的密浑,也就是一個(gè)線程修改的結(jié)果蛙婴,另一個(gè)線程馬上就能看到。
指令重排序: JVM為了優(yōu)化指令尔破、提高程序運(yùn)行效率街图,在不影響單線程程序執(zhí)行結(jié)果的前提下,盡可能地提高并行度懒构;指令重排序包括編譯器重排序和運(yùn)行時(shí)重排序餐济。
注意:volatile不能保證數(shù)據(jù)的原子性。
舉個(gè)?? 胆剧,有一個(gè)變量 i 絮姆,被volatile修飾。兩個(gè)線程想對(duì)這個(gè)變量修改,都對(duì)其進(jìn)行自增操作篙悯,也就是 i++冤灾,這個(gè)過(guò)程可以分為三步:
- 獲取i的值
- 對(duì)i的值進(jìn)行加1
- 最后將得到的新值寫回到緩存中
假設(shè)線程A首先獲取到了i的初始值100,但是還沒(méi)來(lái)得及修改辕近,就阻塞了韵吨,這時(shí)線程B開(kāi)始了,它也得到了i的值移宅,由于i的值未被修改归粉,那么線程B得到的值也是100,之后對(duì)其進(jìn)行加1操作漏峰,得到101后糠悼,將新值寫入到緩存中,再刷入主內(nèi)存中浅乔。根據(jù)可見(jiàn)性的原則倔喂,這個(gè)主存的值可以被其他線程可見(jiàn)。
那么問(wèn)題就來(lái)了靖苇,線程A已經(jīng)讀取到了i的值為100席噩,線程A阻塞結(jié)束后,繼續(xù)將100這個(gè)值加1贤壁,得到101悼枢,再將值寫到緩存,最后刷入主內(nèi)存脾拆,數(shù)據(jù)就不正確了馒索。
所以即便是volatile具有可見(jiàn)性,也不能保證被它修飾的變量具有原子性名船。
對(duì)于禁止指令重排序绰上,在 JAVA 設(shè)計(jì)模式之《單例模式》 中,雙重檢查鎖方式里詳細(xì)講解了渠驼。