Java面試終極概念之「?jìng)喂蚕怼?/h1>

在解釋【偽共享】這個(gè)概念之前争拐,我們先來(lái)運(yùn)行一段代碼康嘉,小編的電腦上有4個(gè)core仆抵。

這個(gè)程序的邏輯是4個(gè)線程共享同一個(gè)數(shù)組讀寫不同下標(biāo)的變量。每個(gè)線程循環(huán)1億次讀寫密浑,也就是+1操作蛙婴。然后統(tǒng)計(jì)4個(gè)線程同時(shí)跑完總共花的時(shí)間。

下面我們來(lái)看看在小編的電腦上運(yùn)行的結(jié)果

然后我把SharingLong里面的注釋代碼去掉尔破,再跑了一下街图。

在性能上注釋前后差別高達(dá)5比1,為什么會(huì)在性能上會(huì)產(chǎn)生如此大的差別呢懒构?

這就是本篇要將的主題【偽共享】餐济,英文名叫False Sharing。而SharingLong里面的注釋行一般稱之為【緩存行填充】胆剧,英文名叫Cache Line Padding絮姆。

首先我們來(lái)計(jì)算一下SharingLong對(duì)象占用的內(nèi)存空間,我們不考慮64位的情景赞赖,Java的對(duì)象都有一個(gè)2個(gè)word的頭部滚朵,第一個(gè)word存儲(chǔ)對(duì)象的hashcode和一些特殊的位標(biāo)志,如GC的分代年齡前域、偏向鎖標(biāo)記等辕近,第二個(gè)word存儲(chǔ)對(duì)象的指針地址,一個(gè)word就是32位匿垄。然后加上v和6個(gè)p變量移宅,總共就是8個(gè)long的長(zhǎng)度,也就是64字節(jié)椿疗。

接下來(lái)我們要引入CPU緩存的概念漏峰。

現(xiàn)代的處理器一般都有3級(jí)緩存結(jié)構(gòu),L1届榄、L2和L3浅乔,CPU直接訪問(wèn)主存是一個(gè)相對(duì)比較慢的操作,所以通過(guò)3級(jí)緩存來(lái)提升訪存性能。我們將3個(gè)緩存當(dāng)成一個(gè)整體來(lái)看待靖苇,它就是CPU緩存席噩。緩存的制造成本非常昂貴,它一般要比主存空間小的多贤壁。

CPU在讀主存的時(shí)候悼枢,會(huì)先將主存的一塊數(shù)據(jù)加載到緩存上,然后在緩存上讀取脾拆。當(dāng)CPU寫主存的時(shí)候馒索,它會(huì)首先寫緩存,在未來(lái)的某個(gè)時(shí)間點(diǎn)再一次性將緩存的數(shù)據(jù)全部刷回主存名船,這樣就可以提高寫操作的性能绰上。因?yàn)橛?jì)算機(jī)程序數(shù)據(jù)操作的局部性,CPU連續(xù)的指令傾向于訪問(wèn)相鄰地址空間的數(shù)據(jù)包帚,所以后續(xù)的讀寫操作有很大的概率可以直接在緩存上拿到數(shù)據(jù)渔期。如果緩存上不存在运吓,那就再去主存上加載進(jìn)來(lái)渴邦。

緩存雖然小,但是也不是太小拘哨,CPU在加載主存數(shù)據(jù)時(shí)谋梭,如果一次性將整個(gè)Cache填滿,但是接下來(lái)的指令訪問(wèn)的數(shù)據(jù)又不在緩存上倦青,就會(huì)導(dǎo)致讀浪費(fèi)瓮床。另外如果只修改了其中幾個(gè)字節(jié)的數(shù)據(jù),但是得回寫整個(gè)Cache到內(nèi)存产镐,這又會(huì)導(dǎo)致寫浪費(fèi)隘庄。

所以現(xiàn)代的CPU緩存一般是分行存儲(chǔ)的,最小處理單位是一個(gè)行癣亚,這個(gè)行的長(zhǎng)度一般來(lái)說(shuō)就是上文提到的64字節(jié)丑掺,我們稱之為【緩存行】。

SharingLong對(duì)象中v的值是volatile類型的述雾,意味著CPU要保證v變量在不同線程之間的讀寫可見(jiàn)行街州。當(dāng)CPU對(duì)v變量進(jìn)行修改的時(shí)候會(huì)將數(shù)據(jù)立即回寫至主存并將相應(yīng)的緩存行置為失效。這樣后續(xù)對(duì)v變量進(jìn)行的讀寫操作都需要重新從內(nèi)存中加載緩存行玻孟,這樣就保證了其它線程讀到的數(shù)據(jù)是最新的唆缴。

這點(diǎn)跟我們平常在Java基礎(chǔ)教科書(shū)里提到的有點(diǎn)不一樣。教科書(shū)里面為了便于新手理解黍翎,不會(huì)提及緩存面徽,一般只會(huì)說(shuō)volatile變量直接讀寫內(nèi)存。

如果內(nèi)存里有兩個(gè)volatile變量在相鄰的地址匣掸,兩個(gè)cpu分別對(duì)v1和v2進(jìn)行讀和寫操作趟紊,會(huì)發(fā)生什么情況呢质礼?首先我們分解執(zhí)行動(dòng)作。圖中的h表示對(duì)象頭织阳。

CPU1對(duì)v1進(jìn)行讀操作眶蕉,將內(nèi)存里的v1加載到緩存行里。

CPU2對(duì)v2進(jìn)行讀操作唧躲,將內(nèi)存里的v2加載到緩存行里造挽。

CPU1對(duì)v1進(jìn)行寫操作,將緩存里的v1修改弄痹,然后回寫到主存再將緩存行置為失效饭入。

CPU2對(duì)v2進(jìn)行寫操作,將緩存里的v2修改肛真,然后回寫到主存再將緩存行置為失效谐丢。

步驟1肯定先于步驟3,步驟2肯定先于步驟4蚓让。它們發(fā)生的順序可能是 1->2->3->4 乾忱,相當(dāng)于兩個(gè)CPU交疊運(yùn)行,步驟1加載緩存行历极,步驟2發(fā)現(xiàn)數(shù)據(jù)就在緩存行里還是最新的窄瘟,就省去了加載緩存行操作了,這時(shí)讀操作做到了【共享】趟卸。緊接著步驟3正常進(jìn)行寫操作蹄葱,然后步驟4來(lái)了,CPU2發(fā)現(xiàn)緩存行失效了锄列,所以還得重新加載緩存行图云,然后再回寫到主存再將緩存行置為失效。這里就發(fā)生了重復(fù)加載緩存行的現(xiàn)象邻邮,也即【寫競(jìng)爭(zhēng)】竣况。如果不是volatile變量,步驟3的寫操作是不會(huì)立即回寫內(nèi)存的饶囚,緩存行也就不會(huì)立即置為失效帕翻,這個(gè)時(shí)候步驟4來(lái)了CPU可以直接對(duì)緩存進(jìn)行寫操作,而不會(huì)出現(xiàn)浪費(fèi)現(xiàn)象萝风。我們稱這種現(xiàn)象為【偽共享】嘀掸,就是說(shuō)這兩個(gè)變量雖然共享同一個(gè)緩存行,但是它們之間會(huì)發(fā)生寫競(jìng)爭(zhēng)规惰。

如果順序是1->3->2->4睬塌,步驟1和步驟3的讀操作這時(shí)就沒(méi)能實(shí)現(xiàn)共享,還是會(huì)有浪費(fèi)。

當(dāng)系統(tǒng)的線程數(shù)越多時(shí)揩晴,寫競(jìng)爭(zhēng)越激烈勋陪,這種浪費(fèi)就越多。

現(xiàn)在我們能明白為什么去掉注釋后硫兰,程序會(huì)變慢诅愚,因?yàn)榇嬖趯懜?jìng)爭(zhēng)現(xiàn)象,數(shù)組中相鄰的SharingLong.v共享了同一個(gè)緩存行劫映。

那加上p1~p6這6個(gè)變量的意義是什么呢违孝?我們看圖。

我們發(fā)現(xiàn)加上6個(gè)long變量后泳赋,v1和v2將分別占用自己的緩存行雌桑,互不干擾,所以寫競(jìng)爭(zhēng)也就不存在了祖今,效率自然就提升了校坑。

不過(guò)缺點(diǎn)也是有的,就是緩存的利用率降低了千诬,一個(gè)緩存行的空間才使用了1/4耍目。這就是典型的空間換時(shí)間的場(chǎng)景。

例子中我們使用了volatile變量大渤,那如果改成普通變量呢制妄?我們運(yùn)行一下,結(jié)果如下泵三。

相當(dāng)驚人,耗時(shí)上居然少了3個(gè)量級(jí)衔掸,這就是volatile在性能上的代價(jià)烫幕。普通變量不需要保證線程之間的讀寫的可見(jiàn)性,CPU對(duì)緩存修改后不需要立即回寫內(nèi)存敞映,不存在寫操作緩存穿透現(xiàn)象较曼。而讀操作也不需要總是重新從內(nèi)存加載,那這個(gè)效率幾乎完全就是緩存訪問(wèn)的效率振愿,而對(duì)volatile變量的讀寫操作則接近內(nèi)存訪問(wèn)的效率捷犹,差距自然如此明顯。

你也許會(huì)問(wèn)冕末,知道這些有什么蛋用萍歉!

確是沒(méi)什么蛋用割岛,因?yàn)樵诂F(xiàn)實(shí)世界譬涡,大部分操作都涉及到IO操作寂殉。根據(jù)水桶效應(yīng),其它環(huán)節(jié)優(yōu)化到了極致租副,也無(wú)法提升整體的質(zhì)量。

但是也不完全所有的應(yīng)用都是IO操作型的皆串,有一些場(chǎng)景下那是純粹的內(nèi)存操作殷费。那么對(duì)于純內(nèi)存操作來(lái)說(shuō),理解【偽共享】知識(shí)可以幫你從性能上提升幾倍甚至是幾個(gè)數(shù)量級(jí)攻询。

著名的disruptor框架正是使用了緩存行填充技術(shù)从撼,才使得它的環(huán)形數(shù)組隊(duì)列能如此高效【埽看wiki上的性能報(bào)告谋逻,disruptor的RingBuffer相比Java內(nèi)置的ArrayBlockingQueue在OPS上高出近一個(gè)數(shù)量級(jí),在隊(duì)列延遲上則低了接近3個(gè)數(shù)量級(jí)桐经。

閱讀相關(guān)文章毁兆,關(guān)注微信公眾號(hào)/知乎專欄/頭條號(hào)【碼洞】

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者

  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市阴挣,隨后出現(xiàn)的幾起案子气堕,更是在濱河造成了極大的恐慌,老刑警劉巖畔咧,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件茎芭,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡誓沸,警方通過(guò)查閱死者的電腦和手機(jī)梅桩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)拜隧,“玉大人宿百,你說(shuō)我怎么就攤上這事『樘恚” “怎么了垦页?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)干奢。 經(jīng)常有香客問(wèn)我痊焊,道長(zhǎng),這世上最難降的妖魔是什么忿峻? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任薄啥,我火速辦了婚禮,結(jié)果婚禮上逛尚,老公的妹妹穿的比我還像新娘垄惧。我一直安慰自己,他們只是感情好黑低,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布赘艳。 她就那樣靜靜地躺著酌毡,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蕾管。 梳的紋絲不亂的頭發(fā)上枷踏,一...
    開(kāi)封第一講書(shū)人閱讀 51,146評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音掰曾,去河邊找鬼旭蠕。 笑死,一個(gè)胖子當(dāng)著我的面吹牛旷坦,可吹牛的內(nèi)容都是我干的掏熬。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼秒梅,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼旗芬!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起捆蜀,我...
    開(kāi)封第一講書(shū)人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤疮丛,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后辆它,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體誊薄,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年锰茉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了呢蔫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡飒筑,死狀恐怖片吊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情扬霜,我是刑警寧澤定鸟,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站著瓶,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏啼县。R本人自食惡果不足惜材原,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望季眷。 院中可真熱鬧余蟹,春花似錦、人聲如沸子刮。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至葵孤,卻和暖如春担钮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背尤仍。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工箫津, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人宰啦。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓苏遥,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親赡模。 傳聞我的和親對(duì)象是個(gè)殘疾皇子田炭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容