1. 現(xiàn)代CPU Cache結構
1.1 緩存的主要作用
現(xiàn)代多核CPU為了提升處理速度肛响,都會將需要的數(shù)據(jù)從內(nèi)存拷貝到各自的緩存中(L1岭粤,L2),然后在各自的緩存中對數(shù)據(jù)進行操作终惑。
1.2 緩存的作用范圍
L1,L2 是CPU中每個核私有的门扇,用于備份各自所需要的數(shù)據(jù)雹有。L3 Cache是CPU每個核共享的偿渡。
1.3 緩存的寫入模式
寫回(write-back)模式:各個核每次修改自己緩存中的數(shù)據(jù)后不會立即寫回到內(nèi)存,而是等到一定合適的時間才寫回到內(nèi)存霸奕。
直寫(write-through)模式:各個核每次修改自己緩存中的數(shù)據(jù)后會立即寫回到內(nèi)存溜宽。
1.4 緩存的一致性
無論哪種寫入模式,試想质帅,如果多個核都從內(nèi)存緩存了一份相同的數(shù)據(jù)适揉,而此時有一個核將自己緩存中的數(shù)據(jù)進行了修改,其他核緩存中的數(shù)據(jù)卻依然是修改之前的數(shù)據(jù)煤惩,這就造成了各個核之間緩存數(shù)據(jù)的不一致嫉嘀,而我們所期望的是各個核緩存之間的數(shù)據(jù)可以同步,為了達到同步的目的魄揉,各個核的緩存需要共同遵守一份協(xié)議剪侮,保證修改共享數(shù)據(jù)的時候可以通知其他緩存,這就是CPU的緩存一致性協(xié)議洛退。
緩存一致性協(xié)議有多種瓣俯,但是基本上都是基于MESI協(xié)議進行擴展的,詳細的內(nèi)容可以查閱相關文檔了解兵怯。
2. volatile的實現(xiàn)原理
2.1 可見性
可見性彩匕,即一個線程修改一個共享變量時,其他線程可以獲取到修改后的值媒区。JVM的實現(xiàn)中驼仪,volatile共享變量的寫操作會向處理器發(fā)送一條Lock指令,聲言使用直寫模式驻仅,在CPU緩存一致性協(xié)議的保證下實現(xiàn)volitile共享變量的可見性谅畅。
簡單來講,多核環(huán)境下噪服,對volatile共享變量進行寫操作會將緩存的結果直接寫回到內(nèi)存中毡泻,在緩存一致性協(xié)議下,其他核會對總線上傳輸?shù)臄?shù)據(jù)一直進行窺探粘优,即監(jiān)測其他核對緩存數(shù)據(jù)的操作仇味。因此,volatile共享變量寫回內(nèi)存的操作會被其他核監(jiān)測到雹顺,此時這些核會將自己緩存中的數(shù)據(jù)設為無效狀態(tài)丹墨,當需要對這個數(shù)據(jù)進行修改操作的時候會重新從內(nèi)存中讀取,這樣就達到了volatile修飾變量的可見性嬉愧。
2.1 原子性
volatile修飾的變量無論是讀還是寫操作贩挣,都是具備原子性的,可以理解為使用了鎖:
volatile int value = 1;
public void set(int value) {
this.value = value;
}
public void get() {
return this.value;
}
int value = 1;
public synchronized void set(int value) {
this.value = value;
}
public synchronized int get() {
return this.value;
}
這意味著多線程環(huán)境下任何一個線程讀取到的volatile修飾的共享變量都是當前的最新值,不會存在差異性王财。
但是有一點需要注意卵迂,volatile只是保證了讀/寫的原子性,復合的volatile操作并不保證原子性绒净,例如:
volatile int value = 1
public void set(int value) {
this.value = value++;
}
因為value++這個操作可以分為三個步驟见咒,首先,讀取value的值挂疆,其次改览,
進行自增操作,最后缤言,將結果寫入內(nèi)存宝当。需要知道這三個操作volatile并不能保證原子性,即只能保證讀取到的value是最新值墨闲,如果此時該處理器讀取value之后由于某些原因阻塞今妄,而此時其他處理器剛好對value進行了修改,這個時候之前的處理器進行計算時還是使用之前讀取到的value鸳碧,這樣就造成了錯誤的處理結果盾鳞。