从缓存行出发理解volatile变量、伪共享False sharing、disruptor
volatile關(guān)鍵字
當(dāng)變量被某個(gè)線程A修改值之后,其它線程比如B若讀取此變量的話,立刻可以看到原來(lái)線程A修改后的值
?
注:普通變量與volatile變量的區(qū)別是volatile的特殊規(guī)則保證了新值能立即同步到主內(nèi)存,以及每次使用前可以立即從內(nèi)存刷新,即一個(gè)線程修改了某個(gè)變量的值,其它線程讀取的話肯定能看到新的值;
普通變量:
寫命中:當(dāng)處理器將操作數(shù)寫回到一個(gè)內(nèi)存緩存的區(qū)域時(shí),它首先會(huì)檢查這個(gè)緩存的內(nèi)存地址是否在緩存行中,如果不存在一個(gè)有效的緩存行,則處理器將這個(gè)操作數(shù)寫回到緩存,而不是寫回到內(nèi)存,這個(gè)操作被稱為寫命中。
術(shù)語(yǔ) | 英文單詞 | 描述 |
共享變量 | ? | 在多個(gè)線程之間能夠被共享的變量被稱為共享變量。共享變量包括所有的實(shí)例變量,靜態(tài)變量和數(shù)組元素。他們都被存放在堆內(nèi)存中,Volatile只作用于共享變量。 |
內(nèi)存屏障 | Memory Barriers | 是一組處理器指令,用于實(shí)現(xiàn)對(duì)內(nèi)存操作的順序限制。 備注:?In the?Java Memory Model?a?volatile?field has a store barrier inserted after a write to it and a load barrier inserted before a read of it. |
緩沖行 | Cache line | 緩存中可以分配的最小存儲(chǔ)單位。處理器填寫緩存線時(shí)會(huì)加載整個(gè)緩存線,需要使用多個(gè)主內(nèi)存讀周期。 |
原子操作 | Atomic operations | 不可中斷的一個(gè)或一系列操作。 |
緩存行填充 | cache line fill | 當(dāng)處理器識(shí)別到從內(nèi)存中讀取操作數(shù)是可緩存的,處理器讀取整個(gè)緩存行到適當(dāng)?shù)木彺?#xff08;L1,L2,L3的或所有) |
緩存命中 | cache hit | 如果進(jìn)行高速緩存行填充操作的內(nèi)存位置仍然是下次處理器訪問(wèn)的地址時(shí),處理器從緩存中讀取操作數(shù),而不是從內(nèi)存。 |
寫命中 | write hit | 當(dāng)處理器將操作數(shù)寫回到一個(gè)內(nèi)存緩存的區(qū)域時(shí),它首先會(huì)檢查這個(gè)緩存的內(nèi)存地址是否在緩存行中,如果不存在一個(gè)有效的緩存行,則處理器將這個(gè)操作數(shù)寫回到緩存,而不是寫回到內(nèi)存,這個(gè)操作被稱為寫命中。 |
寫缺失 | write misses the cache | 一個(gè)有效的緩存行被寫入到不存在的內(nèi)存區(qū)域。 |
單核CPU緩存結(jié)構(gòu)
?
?
單核CPU緩存
多核CPU緩存
所謂緩存航就是緩存中可以分配的最小存儲(chǔ)單位。
處理器填寫緩存線時(shí)會(huì)加載整個(gè)緩存線,需要使用多個(gè)主內(nèi)存讀周期。
下面降到偽緩存時(shí)會(huì)介紹,多核CPU、內(nèi)存的緩存系統(tǒng);
Information transfer between the cache and the memory?is in terms of complete cache lines, rather than individual bytes.?Thus if the program needs a particular byte, the entire cache line containing that byte is obtained from the memory.?For example, suppose that the cache of Figure 2 was being used and the program fetches the word (two bytes) at location 0004736. If none of the cache lines contain the 16 bytes stored in addresses 0004730 through 000473F, then these 16 bytes are transferred from the memory to one of the cache lines. Because of the spatial locality of the program, we expect that other values in the cache line thus loaded will be referenced in the near future.
Volatile的實(shí)現(xiàn)原理
?
那么Volatile是如何來(lái)保證可見性的呢?在x86處理器下通過(guò)工具獲取JIT編譯器生成的匯編指令來(lái)看看對(duì)Volatile進(jìn)行寫操作CPU會(huì)做什么事情。
?
Java代碼: | instance = new Singleton();//instance是volatile變量 |
匯編代碼: | 0x01a3de1d: movb $0x0,0x1104800(%esi); 0x01a3de24:?lock?addl $0x0,(%esp); |
?
?
?
有volatile變量修飾的共享變量進(jìn)行寫操作的時(shí)候會(huì)多第二行匯編代碼,通過(guò)查IA-32架構(gòu)軟件開發(fā)者手冊(cè)可知,lock前綴的指令在多核處理器下會(huì)引發(fā)了兩件事情
- 將當(dāng)前處理器緩存行的數(shù)據(jù)會(huì)寫回到系統(tǒng)內(nèi)存。
- 這個(gè)寫回內(nèi)存的操作會(huì)引起在其他CPU里緩存了該內(nèi)存地址的數(shù)據(jù)無(wú)效。
深度解析: 處理器為了提高處理速度,不直接和內(nèi)存進(jìn)行通訊,而是先將系統(tǒng)內(nèi)存的數(shù)據(jù)讀到內(nèi)部緩存(L1,L2或其他)后再進(jìn)行操作,但操作完之后不知道何時(shí)會(huì)寫到內(nèi)存;如果對(duì)聲明了Volatile變量進(jìn)行寫操作,JVM就會(huì)向處理器發(fā)送一條Lock前綴的指令,將這個(gè)變量所在緩存行的數(shù)據(jù)寫回到系統(tǒng)內(nèi)存。但是就算寫回到內(nèi)存,如果其他處理器緩存的值還是舊的,再執(zhí)行計(jì)算操作就會(huì)有問(wèn)題,所以在多處理器下,為了保證各個(gè)處理器的緩存是一致的,就會(huì)實(shí)現(xiàn)緩存一致性協(xié)議,每個(gè)處理器通過(guò)嗅探在總線上傳播的數(shù)據(jù)來(lái)檢查自己緩存的值是不是過(guò)期了,當(dāng)處理器發(fā)現(xiàn)自己緩存行對(duì)應(yīng)的內(nèi)存地址被修改,就會(huì)將當(dāng)前處理器的緩存行設(shè)置成無(wú)效狀態(tài),當(dāng)處理器要對(duì)這個(gè)數(shù)據(jù)進(jìn)行修改操作的時(shí)候,會(huì)強(qiáng)制重新從系統(tǒng)內(nèi)存里把數(shù)據(jù)讀到處理器緩存里。
Lock前綴指令會(huì)引起處理器緩存回寫到內(nèi)存。Lock前綴指令導(dǎo)致在執(zhí)行指令期間,聲言處理器的 LOCK# 信號(hào)。在多處理器環(huán)境中,LOCK# 信號(hào)確保在聲言該信號(hào)期間,處理器可以獨(dú)占使用任何共享內(nèi)存。(因?yàn)樗鼤?huì)鎖住總線,導(dǎo)致其他CPU不能訪問(wèn)總線,不能訪問(wèn)總線就意味著不能訪問(wèn)系統(tǒng)內(nèi)存),但是在最近的處理器里,LOCK#信號(hào)一般不鎖總線,而是鎖緩存,畢竟鎖總線開銷比較大。在8.1.4章節(jié)有詳細(xì)說(shuō)明鎖定操作對(duì)處理器緩存的影響,對(duì)于Intel486和Pentium處理器,在鎖操作時(shí),總是在總線上聲言LOCK#信號(hào)。但在P6和最近的處理器中,如果訪問(wèn)的內(nèi)存區(qū)域已經(jīng)緩存在處理器內(nèi)部,則不會(huì)聲言LOCK#信號(hào)。相反地,它會(huì)鎖定這塊內(nèi)存區(qū)域的緩存并回寫到內(nèi)存,并使用緩存一致性機(jī)制來(lái)確保修改的原子性,此操作被稱為“緩存鎖定”,緩存一致性機(jī)制會(huì)阻止同時(shí)修改被兩個(gè)以上處理器緩存的內(nèi)存區(qū)域數(shù)據(jù)。
一個(gè)處理器的緩存回寫到內(nèi)存會(huì)導(dǎo)致其他處理器的緩存無(wú)效。IA-32處理器和Intel 64處理器使用MESI(修改,獨(dú)占,共享,無(wú)效)控制協(xié)議去維護(hù)內(nèi)部緩存和其他處理器緩存的一致性。在多核處理器系統(tǒng)中進(jìn)行操作的時(shí)候,IA-32 和Intel 64處理器能嗅探其他處理器訪問(wèn)系統(tǒng)內(nèi)存和它們的內(nèi)部緩存。它們使用嗅探技術(shù)保證它的內(nèi)部緩存,系統(tǒng)內(nèi)存和其他處理器的緩存的數(shù)據(jù)在總線上保持一致。例如在Pentium和P6 family處理器中,如果通過(guò)嗅探一個(gè)處理器來(lái)檢測(cè)其他處理器打算寫內(nèi)存地址,而這個(gè)地址當(dāng)前處理共享狀態(tài),那么正在嗅探的處理器將無(wú)效它的緩存行,在下次訪問(wèn)相同內(nèi)存地址時(shí),強(qiáng)制執(zhí)行緩存行填充。
Volatile的使用優(yōu)化
著名的Java并發(fā)編程大師Doug lea在JDK7的并發(fā)包里新增一個(gè)隊(duì)列集合類LinkedTransferQueue,他在使用Volatile變量時(shí),用一種追加字節(jié)的方式來(lái)優(yōu)化隊(duì)列出隊(duì)和入隊(duì)的性能。
追加字節(jié)能優(yōu)化性能?這種方式看起來(lái)很神奇,但如果深入理解處理器架構(gòu)就能理解其中的奧秘。讓我們先來(lái)看看LinkedTransferQueue這個(gè)類,它使用一個(gè)內(nèi)部類類型來(lái)定義隊(duì)列的頭隊(duì)列(Head)和尾節(jié)點(diǎn)(tail),而這個(gè)內(nèi)部類PaddedAtomicReference相對(duì)于父類AtomicReference只做了一件事情,就將共享變量追加到64字節(jié)。我們可以來(lái)計(jì)算下,一個(gè)對(duì)象的引用占4個(gè)字節(jié),它追加了15個(gè)變量共占60個(gè)字節(jié),再加上父類的Value變量,一共64個(gè)字節(jié)。
為什么追加64字節(jié)能夠提高并發(fā)編程的效率呢? 因?yàn)閷?duì)于英特爾酷睿i7,酷睿, Atom和NetBurst, Core Solo和Pentium M處理器的L1,L2或L3緩存的高速緩存行是64個(gè)字節(jié)寬,不支持部分填充緩存行,這意味著如果隊(duì)列的頭節(jié)點(diǎn)和尾節(jié)點(diǎn)都不足64字節(jié)的話,處理器會(huì)將它們都讀到同一個(gè)高速緩存行中,在多處理器下每個(gè)處理器都會(huì)緩存同樣的頭尾節(jié)點(diǎn),當(dāng)一個(gè)處理器試圖修改頭接點(diǎn)時(shí)會(huì)將整個(gè)緩存行鎖定,那么在緩存一致性機(jī)制的作用下,會(huì)導(dǎo)致其他處理器不能訪問(wèn)自己高速緩存中的尾節(jié)點(diǎn),而隊(duì)列的入隊(duì)和出隊(duì)操作是需要不停修改頭接點(diǎn)和尾節(jié)點(diǎn),所以在多處理器的情況下將會(huì)嚴(yán)重影響到隊(duì)列的入隊(duì)和出隊(duì)效率。Doug lea使用追加到64字節(jié)的方式來(lái)填滿高速緩沖區(qū)的緩存行,避免頭接點(diǎn)和尾節(jié)點(diǎn)加載到同一個(gè)緩存行,使得頭尾節(jié)點(diǎn)在修改時(shí)不會(huì)互相鎖定。
那么是不是在使用Volatile變量時(shí)都應(yīng)該追加到64字節(jié)呢?不是的。在兩種場(chǎng)景下不應(yīng)該使用這種方式。第一:緩存行非64字節(jié)寬的處理器,如P6系列和奔騰處理器,它們的L1和L2高速緩存行是32個(gè)字節(jié)寬。第二:共享變量不會(huì)被頻繁的寫。因?yàn)槭褂米芳幼止?jié)的方式需要處理器讀取更多的字節(jié)到高速緩沖區(qū),這本身就會(huì)帶來(lái)一定的性能消耗,共享變量如果不被頻繁寫的話,鎖的幾率也非常小,就沒(méi)必要通過(guò)追加字節(jié)的方式來(lái)避免相互鎖定。
| /** head of the queue */????private?transient?final?PaddedAtomicReference < QNode > head;????/** tail of the queue */????private?transient?final?PaddedAtomicReference < QNode > tail;????static?final?class?PaddedAtomicReference < T > extends?AtomicReference < T > {????????// enough padding for 64bytes with 4byte refs ????????Object p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, pa, pb, pc, pd, pe;????????PaddedAtomicReference(T r) {????????????super(r);????????}????}????public?class?AtomicReference < V > implements?java.io.Serializable {????????private?volatile?V value;????} |
為什么追加64字節(jié)能夠提高并發(fā)編程的效率呢? 因?yàn)閷?duì)于英特爾酷睿i7,酷睿, Atom和NetBurst, Core Solo和Pentium M處理器的L1,L2或L3緩存的高速緩存行是64個(gè)字節(jié)寬,不支持部分填充緩存行,這意味著如果隊(duì)列的頭節(jié)點(diǎn)和尾節(jié)點(diǎn)都不足64字節(jié)的話,處理器會(huì)將它們都讀到同一個(gè)高速緩存行中,在多處理器下每個(gè)處理器都會(huì)緩存同樣的頭尾節(jié)點(diǎn),當(dāng)一個(gè)處理器試圖修改頭接點(diǎn)時(shí)會(huì)將整個(gè)緩存行鎖定,那么在緩存一致性機(jī)制的作用下,會(huì)導(dǎo)致其他處理器不能訪問(wèn)自己高速緩存中的尾節(jié)點(diǎn),而隊(duì)列的入隊(duì)和出隊(duì)操作是需要不停修改頭接點(diǎn)和尾節(jié)點(diǎn),所以在多處理器的情況下將會(huì)嚴(yán)重影響到隊(duì)列的入隊(duì)和出隊(duì)效率。Doug lea使用追加到64字節(jié)的方式來(lái)填滿高速緩沖區(qū)的緩存行,避免頭接點(diǎn)和尾節(jié)點(diǎn)加載到同一個(gè)緩存行,使得頭尾節(jié)點(diǎn)在修改時(shí)不會(huì)互相鎖定。
那么是不是在使用Volatile變量時(shí)都應(yīng)該追加到64字節(jié)呢?不是的。在兩種場(chǎng)景下不應(yīng)該使用這種方式。第一:緩存行非64字節(jié)寬的處理器,如P6系列和奔騰處理器,它們的L1和L2高速緩存行是32個(gè)字節(jié)寬。第二:共享變量不會(huì)被頻繁的寫。因?yàn)槭褂米芳幼止?jié)的方式需要處理器讀取更多的字節(jié)到高速緩沖區(qū),這本身就會(huì)帶來(lái)一定的性能消耗,共享變量如果不被頻繁寫的話,鎖的幾率也非常小,就沒(méi)必要通過(guò)追加字節(jié)的方式來(lái)避免相互鎖定。
?
Java并發(fā)編程實(shí)踐寫道:“一個(gè)理解volatile變量好的方法:想想它們的行為與SynchrosizedInteger類相似,只不過(guò)用get和set方法取代了對(duì)volatile變量的讀寫操作。然而訪問(wèn)volatile變量的操作不會(huì)加鎖,也就不會(huì)引起線程的阻塞,這使volatile相對(duì)于synchronized而言,只是輕量級(jí)的同步機(jī)制”
?
false-sharing
?
Memory is stored within the cache system in units know as cache lines.? Cache lines are a power of 2 of contiguous bytes which are typically 32-256 in size.? The most common cache line size is 64 bytes.?? False sharing is a term which applies when threads unwittingly impact the performance of each other while modifying independent variables sharing the same cache line.? Write contention on cache lines is the single most limiting factor on achieving scalability for parallel threads of execution in an SMP system.? I’ve heard false sharing described as the silent performance killer because it is far from obvious when looking at code.
class {int x ,int y} ?x和y被放在同一個(gè)高速緩存區(qū),如果一個(gè)線程修改x;那么另外一個(gè)線程修改y,必須等待x修改完成后才能實(shí)施。
?
L1 Cache(一級(jí)緩存)是CPU第一層高速緩存,分為數(shù)據(jù)緩存和指令緩存。內(nèi)置的L1高速緩存的容量和結(jié)構(gòu)對(duì)CPU的性能影響較大,不過(guò)高速緩沖存儲(chǔ)器均由靜態(tài)RAM組成,結(jié)構(gòu)較復(fù)雜,在CPU管芯面積不能太大的情況下,L1級(jí)高速緩存的容量不可能做得太大。一般服務(wù)器CPU的L1緩存的容量通常在32—4096KB。
L2 Cache 都在CPU中
L3 Cache(三級(jí)緩存),分為兩種,早期的是外置,現(xiàn)在的都是內(nèi)置的。而它的實(shí)際作用即是,L3緩存的應(yīng)用可以進(jìn)一步降低內(nèi)存延遲,同時(shí)提升大數(shù)據(jù)量計(jì)算時(shí)處理器的性能。降低內(nèi)存延遲和提升大數(shù)據(jù)量計(jì)算能力對(duì)游戲都很有幫助。而在服務(wù)器領(lǐng)域增加L3緩存在性能方面仍然有顯著的提升。比方具有較大L3緩存的配置利用物理內(nèi)存會(huì)更有效,故它比較慢的磁盤I/O子系統(tǒng)可以處理更多的數(shù)據(jù)請(qǐng)求。具有較大L3緩存的處理器提供更有效的文件系統(tǒng)緩存行為及較短消息和處理器隊(duì)列長(zhǎng)度。
從圖中可以看出為多核共享的
To achieve linear scalability with number of threads, we must ensure no two threads write to the same variable or cache line.? Two threads writing to the same variable can be tracked down at a code level.?? To be able to know if independent variables share the same cache line we need to know the memory layout, or we can get a tool to tell us.? Intel VTune is such a profiling tool.? In this article I’ll explain how memory is laid out for Java objects and how we can pad out our cache lines to avoid false sharing.
?
可見每個(gè)cpu核心或者線程都會(huì)可能往同一個(gè)緩存行寫數(shù)據(jù);并且對(duì)共享變量,同時(shí)cpu核心會(huì)有各自的緩存行
雖然兩個(gè)線程修改各種獨(dú)立變量,但是因?yàn)檫@些獨(dú)立變量被放在同一個(gè)高速緩存區(qū),性能就影響了。測(cè)試結(jié)果。
當(dāng)多核CPU線程同時(shí)修改在同一個(gè)高速緩存行各自獨(dú)立的變量時(shí),會(huì)不自不覺(jué)地影響性能,這就發(fā)生了偽共享False sharing,偽共享是性能的無(wú)聲殺手。
?
當(dāng)多核CPU線程同時(shí)修改在同一個(gè)高速緩存行各自獨(dú)立的變量時(shí),會(huì)不自不覺(jué)地影響性能,這就發(fā)生了偽共享False sharing,偽共享是性能的無(wú)聲殺手。
這里強(qiáng)調(diào)多核,是因?yàn)閱魏薈PU模擬出的多線程不會(huì)嚴(yán)格意義上同時(shí)訪問(wèn)緩存行,所以性能影響不大
解決方便是將高速緩存剩余的字節(jié)填充填滿(pad),確保不發(fā)生多個(gè)字段被擠入一個(gè)高速緩存區(qū),下面測(cè)試結(jié)果圖就是和填充后性能比較。
實(shí)現(xiàn)字節(jié)填充的框架有 Disruptor,在RingBuffer中實(shí)現(xiàn)填充。關(guān)于Disruptor可見infoQ這個(gè)視頻,用1毫秒的延時(shí)得到100K+ TPS吞吐量,
JDK的ArrayQueue并行環(huán)境不見得是最快的,該視頻后面討論很多,讓人大跌眼鏡啊,開放源碼多有好處啊,別人能發(fā)現(xiàn)你不能發(fā)現(xiàn)的漏洞。
Disruptor
?
Disruptor沒(méi)有像JDK的LinkedBlockQueue等那樣使用鎖,針對(duì)CPU高速緩存進(jìn)行了優(yōu)化。
原來(lái)我們以為多個(gè)線程同時(shí)寫一個(gè)類的字段會(huì)發(fā)生爭(zhēng)奪,這是多線程基本原理,所以使用了鎖機(jī)制,保證這個(gè)共用字段(資源)能夠某個(gè)時(shí)刻只能一個(gè)線程寫,但是這樣做的壞處是:有可能發(fā)生死鎖。
比如1號(hào)線程先后訪問(wèn)共享資源A和B;而2號(hào)線程先后訪問(wèn)共享資源B和A,因?yàn)樵谫Y源A和資源B都有鎖,那么1號(hào)在訪問(wèn)資源A時(shí),資源A上鎖了,準(zhǔn)備訪問(wèn)資源B,但是無(wú)法訪問(wèn),因?yàn)榕c此同時(shí);而2號(hào)線程在訪問(wèn)資源B,資源B鎖著呢,正準(zhǔn)備訪問(wèn)資源A,發(fā)現(xiàn)資源A被1號(hào)線程鎖著呢,結(jié)果彼此無(wú)限等待彼此下去,死鎖類似邏輯上自指悖論。
所以,鎖是壞的,破壞性能,鎖是并發(fā)計(jì)算的大敵。
我們回到隊(duì)列上,一把一個(gè)隊(duì)列有至少兩個(gè)線程:生產(chǎn)者和消費(fèi)者,這就具備了資源爭(zhēng)奪的前提,這兩個(gè)線程一般彼此守在隊(duì)列的進(jìn)出兩端,表面上好像沒(méi)有訪問(wèn)共享資源,實(shí)際上隊(duì)列存在兩個(gè)共享資源:隊(duì)列大小或指針.
除了共享資源寫操作上存在資源爭(zhēng)奪問(wèn)題外,Disruptor的LMAX團(tuán)隊(duì)發(fā)現(xiàn)Java在多核CPU情況下有偽共享問(wèn)題:
CPU會(huì)把數(shù)據(jù)從內(nèi)存加載到高速緩存中 ,這樣可以獲得更好的性能,高速緩存默認(rèn)大小是64 Byte為一個(gè)區(qū)域,CPU機(jī)制限制只能一個(gè)CPU的一個(gè)線程訪問(wèn)(寫)這個(gè)高速緩存區(qū)。
CPU在將主內(nèi)存中數(shù)據(jù)加載到高速緩沖時(shí),如果發(fā)現(xiàn)被加載的數(shù)據(jù)不足64字節(jié),那么就會(huì)加載多個(gè)數(shù)據(jù),以填滿自己的64字節(jié),悲催就發(fā)生了,恰恰hotspot JVM中對(duì)象指針等大小都不會(huì)超過(guò)64字節(jié),這樣一個(gè)高速緩沖中可能加載了兩個(gè)對(duì)象指針,一個(gè)CPU一個(gè)高速緩沖,雙核就是兩個(gè)CPU各自一個(gè)高速緩沖,那么兩個(gè)高速緩沖中各有兩個(gè)對(duì)象指針,都是指向相同的兩個(gè)對(duì)象。
因?yàn)橐粋€(gè)CPU只能訪問(wèn)(寫)自己高速緩存區(qū)中數(shù)據(jù),相當(dāng)于給這個(gè)數(shù)據(jù)加鎖,那么另外一個(gè)CPU同時(shí)訪問(wèn)自己高速緩沖行中同樣數(shù)據(jù)時(shí)將會(huì)被鎖定不能訪問(wèn)。
這就發(fā)生與鎖機(jī)制類似的性能陷進(jìn),Disruptor的解決辦法是填滿高速緩沖的64字節(jié),不是對(duì)象指針等數(shù)據(jù)不夠64字節(jié)嗎?那么加一些字節(jié)填滿64字節(jié),這樣CPU將數(shù)據(jù)加載到高速緩沖時(shí),就只能加載一個(gè)了,剛剛好啊。
所以,盡管兩個(gè)線程是在寫兩個(gè)不同的字段值,也會(huì)因?yàn)殡p核CPU底層機(jī)制發(fā)生偽裝的共享,并沒(méi)有真正共享,其實(shí)還是排他性的獨(dú)享。
現(xiàn)在我們大概知道RingBuffer是個(gè)什么東東了:
1.ring buffer是一個(gè)大的數(shù)組.
2.RingBuffer里所有指針都是Java longs (64字節(jié)) 不斷永遠(yuǎn)向前計(jì)數(shù),如后面圖,不斷在圓環(huán)中循環(huán)。
3.RingBuffer只有當(dāng)前序列號(hào),沒(méi)有終點(diǎn)序列號(hào),其中數(shù)據(jù)不會(huì)被取出后消除,這樣以便實(shí)現(xiàn)從過(guò)去某個(gè)序列號(hào)到當(dāng)前序列號(hào)的重放,這樣當(dāng)消費(fèi)者說(shuō)沒(méi)有接受到生產(chǎn)者發(fā)送的消息,生產(chǎn)者還可以再次發(fā)送,這點(diǎn)是一種原子性的“事務(wù)”機(jī)制。
This new pattern is an ideal foundation for any asynchronous event processing architecture where high-throughput and low-latency is required.
?
Concurrent execution of code is about two things, mutual exclusion and visibility of change.
Read and write operations require that all changes are made visible to other threads. However only contended write operations require the mutual exclusion of the changes.
?
Locks provide mutual exclusion and ensure that the visibility of change occurs in an ordered manner. Locks are incredibly expensive because they require arbitration when contended.
?
We will illustrate the cost of locks with a simple demonstration. The focus of this experiment is to call a function which increments a 64-bit counter in a loop 500 million times. This can be executed by a single thread on a 2.4Ghz Intel Westmere EP in just 300ms if written in Java. The language is unimportant to this experiment and results will be similar across all languages with the same basic primitives.
Once a lock is introduced to provide mutual exclusion, even when the lock is as yet un-contended, the cost goes up significantly. The cost increases again, by orders of magnitude, when two or more threads begin to contend. The results of this simple experiment are shown in the table below
?
However CAS operations are not free of cost. The processor must lock its instruction pipeline to ensure atomicity and employ a memory barrier to make the changes visible to other threads. CAS operations are available in Java by using the java.util.concurrent.Atomic* classes.
The processors need only guarantee that program logic produces the same results regardless of execution order.
?
barriter:make the memory state within a processor visible to other processors.
disruptor 是為了解決消費(fèi)者大于生產(chǎn)者的問(wèn)題
參考
?
偽共享 :http://mechanical-sympathy.blogspot.com/2011/07/false-sharing.html
http://www.jdon.com/jivejdon/thread/42451#23138636
volatile:http://www.infoq.com/cn/articles/ftf-java-volatile
緩存行:http://ecee.colorado.edu/~ecen2120/Manual/caches/cache.html
disruptor:http://mechanitis.blogspot.com/2011/07/dissecting-disruptor-why-its-so-fast_22.html
源碼:http://code.google.com/p/disruptor/
memory barrier 屏障:http://mechanical-sympathy.blogspot.com/2011/07/memory-barriersfences.html
總結(jié)
以上是生活随笔為你收集整理的从缓存行出发理解volatile变量、伪共享False sharing、disruptor的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: HBase最佳实践-读性能优化策略
- 下一篇: JVM调优系列:(二)JVM运行时数据区