Java作為一個(gè)跨平臺的語言,它的實(shí)現(xiàn)要面對不同的底層硬件系統(tǒng)自沧,設(shè)計(jì)一個(gè)中間層模型來屏蔽底層的硬件差異纵柿,給上層的開發(fā)者一個(gè)一致的使用接口。Java內(nèi)存模型就是這樣一個(gè)中間層的模型衩茸,它為程序員屏蔽了底層的硬件實(shí)現(xiàn)細(xì)節(jié)芹血,支持大部分的主流硬件平臺。要理解Java內(nèi)存模型以及一些處理高并發(fā)的技術(shù)手段楞慈,理解一些基本的硬件知識是必須的幔烛。這篇會說一下跟并發(fā)編程相關(guān)的一些硬件知識。
一個(gè)基本的CPU執(zhí)行計(jì)算的過程如下:
1. 程序以及數(shù)據(jù)被加載到主內(nèi)存
2. 指令和數(shù)據(jù)被加載到CPU的高速緩存
3. CPU執(zhí)行指令,把結(jié)果寫到高速緩存
4. 高速緩存中的數(shù)據(jù)寫回主內(nèi)存
這個(gè)過程中,我們可以看到有兩個(gè)問題
1. 現(xiàn)代的計(jì)算芯片都會集成一個(gè)L1高速緩存榨馁,我們可以理解為每個(gè)芯片都有一個(gè)私有的存儲空間耕驰。那么當(dāng)CPU的不同計(jì)算芯片要訪問同一個(gè)內(nèi)存地址時(shí)爵卒,該內(nèi)存地址的值會在CPU的不同計(jì)算芯片之間有多個(gè)拷貝地消,如何同步這些拷貝废赞?
2. CPU讀寫是直接和高速緩存打交道虎眨,而不是和主內(nèi)存直接打交道弟劲。因?yàn)橥ǔR淮沃鞔嬖L問在幾十到幾百個(gè)時(shí)鐘周期祷安,而一次L1高速緩存的讀寫只需要1-2個(gè)時(shí)鐘周期,而一次L2高速緩存的讀寫只需要數(shù)十個(gè)時(shí)鐘周期兔乞。那么CPU寫到高速緩存的值何時(shí)寫回到主內(nèi)汇鞭?如果是多個(gè)計(jì)算芯片在處理同一個(gè)內(nèi)存地址,那么如何處理這個(gè)時(shí)間差是個(gè)問題庸追。
對于第一個(gè)問題霍骄,不同的硬件結(jié)構(gòu)處理的方式不一樣。我們來理解一下互連線的概念淡溯。
互連線是處理器于主存以及處理器與處理器之間進(jìn)行通信的媒介读整,有兩種基本的互聯(lián)結(jié)構(gòu):SMP(symmetric multiprocessing 對稱多處理)和NUMA(nonuniform memory access 非一致內(nèi)存訪問)
SMP系統(tǒng)結(jié)構(gòu)非常普通,因?yàn)樗鼈冏钊菀讟?gòu)建咱娶,很多小型服務(wù)器采用這種結(jié)構(gòu)米间。處理器和存儲器之間采用總線互聯(lián),處理器和存儲器都有負(fù)責(zé)發(fā)送和監(jiān)聽總線廣播的信息的總線控制單元豺总。但是同一時(shí)刻只能有一個(gè)處理器(或存儲控制器)在總線上廣播车伞,所有的處理器都可以監(jiān)聽。
很容易看出喻喳,對總線的使用是SMP結(jié)構(gòu)的瓶頸另玖。
NUMP系統(tǒng)結(jié)構(gòu)中,一系列節(jié)點(diǎn)通過點(diǎn)對點(diǎn)網(wǎng)絡(luò)互聯(lián)表伦,像一個(gè)小型互聯(lián)網(wǎng)谦去,每個(gè)節(jié)點(diǎn)包含一個(gè)或多個(gè)處理器和一個(gè)本地存儲器。一個(gè)節(jié)點(diǎn)的本地存儲對于其他節(jié)點(diǎn)是可見的蹦哼,所有節(jié)點(diǎn)的本地存儲一起形成了一個(gè)可以被所有處理器共享的全局存儲器鳄哭。可以看出纲熏,NUMP的本地存儲是共享的妆丘,而不是私有的,這點(diǎn)和SMP是不同的局劲。NUMP的問題是網(wǎng)絡(luò)比總線復(fù)制勺拣,需要更加復(fù)雜的協(xié)議,處理器訪問自己節(jié)點(diǎn)的存儲器速度快于訪問其他節(jié)點(diǎn)的存儲器鱼填。NUMP的擴(kuò)展性很好药有,所以目前很多大中型的服務(wù)器在采用NUMP結(jié)構(gòu)。
對于上層程序員來說,最需要理解的是互連線是一種重要的資源愤惰,使用的好壞會直接影響程序的執(zhí)行性能苇经。
大概理解了不同的互連結(jié)構(gòu)之后,我們來看看緩存一致性協(xié)議宦言。它主要就是處理多個(gè)處理器處理同一個(gè)主存地址的問題扇单。
MESI是一種主流的緩存一致性協(xié)議,已經(jīng)用在Pentium和PowerPC處理器中蜡励。它定義了緩存塊的幾種狀態(tài)
- modified(修改):緩存塊已經(jīng)被修改令花,必須被寫回主存,其他處理器不能再緩存這個(gè)塊
- exclusive(互斥):緩存塊還沒有被修改凉倚,且其他處理器不能裝入這個(gè)緩存塊
- share(共享):緩存塊未被修改,且其他處理器可以裝入這個(gè)緩存塊
- invalid(無效):緩存塊中的數(shù)據(jù)無效
上圖展示了MESI高速緩存一致性協(xié)議的狀態(tài)轉(zhuǎn)換實(shí)例嫂沉。
1. 在a中稽寒,處理器A從地址a讀取數(shù)據(jù),將數(shù)據(jù)存入它的緩存并置為exclusive
2. 在b中趟章,當(dāng)處理器B試圖從相同地址a讀取數(shù)據(jù)時(shí)杏糙,A檢測到地址沖突,以相關(guān)數(shù)據(jù)做出響應(yīng)蚓土。此時(shí)a同時(shí)被A和B以shared狀態(tài)裝入緩存
3. 在c中宏侍,當(dāng)B要對共享地址a進(jìn)行寫操作,則將狀態(tài)改為modified,并廣播提醒A,讓它將它的緩存塊狀態(tài)設(shè)置為Invalid
4. 在d中蜀漆,當(dāng)A試圖從a讀取數(shù)據(jù)谅河,會廣播它的請求,B則把它修改的數(shù)據(jù)發(fā)送到A和主存确丢,并設(shè)置兩個(gè)副本的狀態(tài)為shared來做出響應(yīng)
更多緩存一致性協(xié)議的細(xì)節(jié)參考這篇 http://blog.csdn.net/realxie/article/details/7317630
緩存一致性協(xié)議存在的一個(gè)最大的問題是可能引起緩存一致性流量風(fēng)暴绷耍,之前我們看到總線在同一時(shí)刻只能被一個(gè)處理器使用,當(dāng)有大量緩存被修改鲜侥,或者同一個(gè)緩存塊一直被修改時(shí)褂始,會產(chǎn)生大量的緩存一致性流量,從而占用總線描函,影響了其他正常的讀寫請求崎苗。
一個(gè)最常見的例子就是如果多個(gè)線程對同一個(gè)變量一直使用CAS操作,那么會有大量修改操作舀寓,從而產(chǎn)生大量的緩存一致性流量胆数,因?yàn)槊恳淮蜟AS操作都會發(fā)出廣播通知其他處理器,從而影響程序的性能基公。
后面我們會講如何優(yōu)化這種使用方式幅慌。
對于第二個(gè)問題,如何處理修改數(shù)據(jù)從高速緩存到主內(nèi)存的時(shí)間差轰豆,通常使用內(nèi)存屏障來處理胰伍,后面會有專門的主題齿诞。