前言
在上篇介紹LongAdder的文章中冤馏,我們最后留下了一個(gè)問(wèn)題,為什么Cell中要插入很多個(gè)實(shí)際上并沒有使用的Long變量寄啼?這個(gè)問(wèn)題就得從False Sharing和Cache line開始說(shuō)起逮光。首先我們得知道Cache line是啥,推薦兩篇文章:文章1和文章2墩划。
科普False Sharing
在有了Cache line基礎(chǔ)之后涕刚,讓我們看看一篇介紹False Sharing的文章,這篇文章介紹了False Sharing以及簡(jiǎn)單說(shuō)明了java8搞出的@Contented乙帮,翻譯如下:
——————————————翻譯start———————————————
java 8中引入了一個(gè)新注解 @Contented,主要是用來(lái)減少“False sharing”杜漠,這篇文章主要講述了@Contented,解釋 了"False sharing"如何成為了性能殺手蚣旱。
"Cache Line"簡(jiǎn)介
CPU不是按單個(gè)bytes來(lái)讀取內(nèi)存數(shù)據(jù)的碑幅,而是以“塊數(shù)據(jù)”的形式,每塊的大小通常為64bytes塞绿,這些“塊”被成為“Cache Line”(這種說(shuō)法其實(shí)很不太正確沟涨,關(guān)于Cache Line的知識(shí)請(qǐng)參考文末的參考鏈接)
如果有兩個(gè)線程(Thread1 和 Thread2)同時(shí)修改一個(gè)volatile數(shù)據(jù),把這個(gè)數(shù)據(jù)記為'x':
volatile long x;
如果線程1打算更改x的值异吻,而線程2準(zhǔn)備讀裙啊:
Thread1:x=3;
Thread2: System.out.println(x);
由于x值被更新了,所以x值需要在線程1和線程2之間傳遞(從線程1到線程2)诀浪,x的變更會(huì)引起整塊64bytes被交換棋返,因?yàn)閏pu核之間以cache lines的形式交換數(shù)據(jù)(cache lines的大小一般為64bytes)。有可能線程1和線程2在同一個(gè)核心里處理雷猪,但是在這個(gè)簡(jiǎn)單的例子中我們假設(shè)每個(gè)線程在不同的核中被處理睛竣。
我們知道long values的內(nèi)存長(zhǎng)度為8bytes,在我們例子中"Cache Line"為64bytes求摇,所以一個(gè)cache line可以存儲(chǔ)8個(gè)long型變量射沟,在cache line中已經(jīng)存儲(chǔ)了一個(gè)long型變量x殊者,我們假設(shè)cache line中剩余的空間用來(lái)存儲(chǔ)了7個(gè)long型變量,例如從v1到v7
x,v1,v2,v3,v4,v5,v6,v7
False Sharing
一個(gè)cache lien可以被多個(gè)不同的線程所使用验夯。如果有其他線程修改了v2的值猖吴,線程1和線程2將會(huì)強(qiáng)制重新加載cache line。你可以會(huì)疑惑我們只是修改了v2的值不應(yīng)該會(huì)影響其他變量挥转,為啥線程1和線程2需要重新加載cache line呢海蔽。然后,即使對(duì)于多個(gè)線程來(lái)說(shuō)這些更新操作是邏輯獨(dú)立的绑谣,但是一致性的保持是以cache line為基礎(chǔ)的党窜,而不是以單個(gè)獨(dú)立的元素。這種明顯沒有必要的共享數(shù)據(jù)的方式被稱作“False sharing”.
Padding
為了獲取一個(gè)cache line域仇,核心需要執(zhí)行幾百個(gè)指令刑然。
如果核心需要等待一個(gè)cache line重新加載,核心將會(huì)停止做其他事情暇务,這種現(xiàn)象被稱為"Stall".Stalls可以通過(guò)減少“False Sharing”,一個(gè)減少"false sharing"的技巧是填充數(shù)據(jù)結(jié)構(gòu)泼掠,使得線程操作的變量落入到不同的cache line中。
下面是一個(gè)填充了的數(shù)據(jù)結(jié)構(gòu)的例子垦细,嘗試著把x和v1放入到不同的cache line中
public class FalseSharingWithPadding {
public volatile long x;
public volatile long p2; // padding
public volatile long p3; // padding
public volatile long p4; // padding
public volatile long p5; // padding
public volatile long p6; // padding
public volatile long p7; // padding
public volatile long p8; // padding
public volatile long v1;
}
在你準(zhǔn)備填充你的所有數(shù)據(jù)結(jié)構(gòu)之前择镇,你必須了解jvm會(huì)減少或者重排序沒有使用的字段,因此可能會(huì)重新引入“false sharing”括改。因此對(duì)象會(huì)在堆中的位置是沒有辦法保證的腻豌。
為了減少未使用的填充字段被優(yōu)化掉的機(jī)會(huì),將這些字段設(shè)置成為volatile會(huì)很有幫助嘱能。對(duì)于填充的建議是你只需要在高度競(jìng)爭(zhēng)的并發(fā)類上使用填充吝梅,并且在你的目標(biāo)架構(gòu)上測(cè)試使用有很大提升之后采用填充。最好的方式是做10000玄幻迭代惹骂,消除JVM的實(shí)時(shí)優(yōu)化的影響苏携。
java8 和 @Contended
比起引入填充字段,一個(gè)更加簡(jiǎn)單有效的方式是在你需要避免“false sharing”的字段上標(biāo)記注解对粪,這可以暗示虛擬機(jī)“這個(gè)字段可以分離到不同的cache line中”右冻,這是JEP 142的目標(biāo)。
JEP引入了 @Contended 注解著拭。
public class Point {
int x;
@Contended
int y;
}
以上代碼使得x和y都在不同的cache line中纱扭。@Contended 使得y字段遠(yuǎn)離了對(duì)象頭部分。
————————————————翻譯end——————————————————
False Sharing在java6/7中
如何避免False Sharing在java 6 7 8 中有不同的實(shí)現(xiàn)方式儡遮, 這篇文章對(duì)比了在6/7/8下面的實(shí)現(xiàn)乳蛾。國(guó)內(nèi)的多篇關(guān)于偽共享的文章基本都來(lái)源于Martin的兩篇博客。
博客1和博客2,博客1主要介紹了什么是False Sharing以及怎么避免False Sharing(在java6的環(huán)境下)屡久,我在看完這篇文文章后使用他的testbench進(jìn)行了測(cè)試忆首,得到的結(jié)果是在java6環(huán)境下,使用6個(gè)long變量進(jìn)行填充是不一定能完全避免false sharing被环,但是我使用了
public final static class VolatileLong {
public volatile long q1, q2, q3, q4, q5, q6, q7;
public volatile long value = 0L;
public volatile long p1, p2, p3, p4, p5, p6, p7;
}
這種方式得到的結(jié)果是完全能夠避免false sharing,我以此郵件了作者M(jìn)artin Thompson說(shuō)明此問(wèn)題详幽,Martin Thompson很快回了郵件附上了博客2的鏈接問(wèn)我是否看過(guò)博客2的內(nèi)容筛欢,我讀過(guò)之后發(fā)現(xiàn)博客2寫的是在java7的環(huán)境下虛擬機(jī)層面會(huì)對(duì)沒有使用的變量進(jìn)行優(yōu)化,所以會(huì)導(dǎo)致false sharing的問(wèn)題唇聘,我覺得這是一個(gè)新的問(wèn)題并不能解釋我在java6環(huán)境下發(fā)生的現(xiàn)象版姑。在java7環(huán)境下要使用填充的方式避免false sharing需要繞很多彎彎而且并不一定能夠達(dá)到效果。所以我覺得我們不能通過(guò)這種“黑科技”解決false sharing的問(wèn)題迟郎,包括Martin Thompson的很多人都希望jvm的開發(fā)團(tuán)隊(duì)能夠搞出一套機(jī)制能夠支持在上層決定多個(gè)字段是否可以出現(xiàn)在同一個(gè)cache line剥险,所以應(yīng)大家的響應(yīng),在java8中宪肖,jvm團(tuán)隊(duì)搞出了@Contended注解來(lái)進(jìn)行支持
java8中的@Contended
關(guān)于@Contended的用法表制,我們可以參考一個(gè)鏈接,這是jvm團(tuán)隊(duì)內(nèi)部關(guān)于JEP-142實(shí)現(xiàn)的一個(gè)郵件回復(fù)控乾,雖然可能和具體實(shí)現(xiàn)有所差別么介,但是參考價(jià)值很大。所以LongAdder在java8中的實(shí)現(xiàn)已經(jīng)采用了@Contended
總結(jié)
這是一個(gè)關(guān)于false sharing的參考文檔的大雜燴蜕衡,沒啥自己的理解壤短。我的建議就是要避免false sharing就在java8環(huán)境下使用@Contended。下篇終于要介紹HystrixRollingNumber了