1. 現(xiàn)代CPU Cache結(jié)構(gòu)

1.1 緩存的主要作用
現(xiàn)代多核CPU為了提升處理速度,都會(huì)將需要的數(shù)據(jù)從內(nèi)存拷貝到各自的緩存中(L1,L2),然后在各自的緩存中對(duì)數(shù)據(jù)進(jìn)行操作。
1.2 緩存的作用范圍
L1,L2 是CPU中每個(gè)核私有的,用于備份各自所需要的數(shù)據(jù)。L3 Cache是CPU每個(gè)核共享的。
1.3 緩存的寫入模式
寫回(write-back)模式:各個(gè)核每次修改自己緩存中的數(shù)據(jù)后不會(huì)立即寫回到內(nèi)存,而是等到一定合適的時(shí)間才寫回到內(nèi)存。
直寫(write-through)模式:各個(gè)核每次修改自己緩存中的數(shù)據(jù)后會(huì)立即寫回到內(nèi)存。
1.4 緩存的一致性
無論哪種寫入模式,試想,如果多個(gè)核都從內(nèi)存緩存了一份相同的數(shù)據(jù),而此時(shí)有一個(gè)核將自己緩存中的數(shù)據(jù)進(jìn)行了修改,其他核緩存中的數(shù)據(jù)卻依然是修改之前的數(shù)據(jù),這就造成了各個(gè)核之間緩存數(shù)據(jù)的不一致,而我們所期望的是各個(gè)核緩存之間的數(shù)據(jù)可以同步,為了達(dá)到同步的目的,各個(gè)核的緩存需要共同遵守一份協(xié)議,保證修改共享數(shù)據(jù)的時(shí)候可以通知其他緩存,這就是CPU的緩存一致性協(xié)議。
緩存一致性協(xié)議有多種,但是基本上都是基于MESI協(xié)議進(jìn)行擴(kuò)展的,詳細(xì)的內(nèi)容可以查閱相關(guān)文檔了解。
2. volatile的實(shí)現(xiàn)原理
2.1 可見性
可見性,即一個(gè)線程修改一個(gè)共享變量時(shí),其他線程可以獲取到修改后的值。JVM的實(shí)現(xiàn)中,volatile共享變量的寫操作會(huì)向處理器發(fā)送一條Lock指令,聲言使用直寫模式,在CPU緩存一致性協(xié)議的保證下實(shí)現(xiàn)volitile共享變量的可見性。
簡單來講,多核環(huán)境下,對(duì)volatile共享變量進(jìn)行寫操作會(huì)將緩存的結(jié)果直接寫回到內(nèi)存中,在緩存一致性協(xié)議下,其他核會(huì)對(duì)總線上傳輸?shù)臄?shù)據(jù)一直進(jìn)行窺探,即監(jiān)測(cè)其他核對(duì)緩存數(shù)據(jù)的操作。因此,volatile共享變量寫回內(nèi)存的操作會(huì)被其他核監(jiān)測(cè)到,此時(shí)這些核會(huì)將自己緩存中的數(shù)據(jù)設(shè)為無效狀態(tài),當(dāng)需要對(duì)這個(gè)數(shù)據(jù)進(jìn)行修改操作的時(shí)候會(huì)重新從內(nèi)存中讀取,這樣就達(dá)到了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)境下任何一個(gè)線程讀取到的volatile修飾的共享變量都是當(dāng)前的最新值,不會(huì)存在差異性。
但是有一點(diǎn)需要注意,volatile只是保證了讀/寫的原子性,復(fù)合的volatile操作并不保證原子性,例如:
volatile int value = 1
public void set(int value) {
this.value = value++;
}
因?yàn)関alue++這個(gè)操作可以分為三個(gè)步驟,首先,讀取value的值,其次,
進(jìn)行自增操作,最后,將結(jié)果寫入內(nèi)存。需要知道這三個(gè)操作volatile并不能保證原子性,即只能保證讀取到的value是最新值,如果此時(shí)該處理器讀取value之后由于某些原因阻塞,而此時(shí)其他處理器剛好對(duì)value進(jìn)行了修改,這個(gè)時(shí)候之前的處理器進(jìn)行計(jì)算時(shí)還是使用之前讀取到的value,這樣就造成了錯(cuò)誤的處理結(jié)果。