1. JMM簡介:
JMM表示了主內(nèi)存和工作內(nèi)存之間的關(guān)系:
1. 主內(nèi)存:只有一份,保存著所有變量。
即計算機內(nèi)存條、即堆
2. 線程的工作內(nèi)存:每線程都有一份且對其他線程不可見,保存著從主存中的變量的副本(其實是變量的引用)。
即計算機單核CPU的寄存器+單核CPU的高速緩存、即棧
3. 線程通信(線程之間變量的傳遞),需要工作內(nèi)存(CPU)——>主內(nèi)存——>工作內(nèi)存(CPU)

2. 高速緩存:
因為cpu速度遠遠大于內(nèi)存速度,為了不讓CPU從主存中讀寫數(shù)據(jù),所以產(chǎn)生了高速(多級)緩存,CPU計算之后從高速(多級)緩存將結(jié)果寫入主存中。
CPU查找順序依次是L1、L2、L3、主存。
其中L1是L2的子集,L2是L3的子集,L1到L3緩存容量依次增大,查找耗時依次增大。
L1與CPU core對應(yīng),是單核獨占的,不會出現(xiàn)其他核修改的問題。一般L2也是單核獨占。而L3在同插槽的所有CPU共享的,有可能操作同一份數(shù)據(jù)。

3. 偽共享:
3.1 偽共享的概念:
高速緩存是以緩存行(cacheline)為單位存儲的。
最常見的緩存行大小是64個字節(jié)(32位CPU就是32字節(jié)?),并且它有效地引用主內(nèi)存中的一塊地址。
一個 Java 的 long 類型是 8 字節(jié),因此在一個緩存行中可以存 8 個 long 類型的變量。
所以,如果你訪問一個 long 數(shù)組,當數(shù)組中的一個值被加載到緩存中,它會額外加載另外 7 個,以致你能非常快地遍歷這個數(shù)組。
而如果你在數(shù)據(jù)結(jié)構(gòu)中的項在內(nèi)存中不是彼此相鄰的(如鏈表),你將得不到免費緩存加載所帶來的優(yōu)勢,并且在這些數(shù)據(jù)結(jié)構(gòu)中的每一個項都可能會出現(xiàn)緩存未命中。
如果存在這樣的場景,有多個線程操作不同的成員變量,但是相同的緩存行,這個時候會發(fā)生什么?。沒錯,偽共享(False Sharing)問題就發(fā)生了!
偽共享:如果多個核的線程在操作同一個緩存行中的不同變量數(shù)據(jù),那么就會出現(xiàn)頻繁的緩存失效,
這種不合理的資源競爭情況學名偽共享(False Sharing),會嚴重影響機器的并發(fā)執(zhí)行效率。

3.2 偽共享缺點場景:
上圖中,一個運行在處理器 core1上的線程想要更新變量 X 的值,同時另外一個運行在處理器 core2 上的線程想要更新變量 Y 的值。
但是,這兩個頻繁改動的變量都處于同一條緩存行。
兩個線程就會輪番發(fā)送 RFO 消息,占得此緩存行的擁有權(quán)。
當 core1 取得了擁有權(quán)開始更新 X,則 core2 對應(yīng)的緩存行需要設(shè)為 I 狀態(tài)。
當 core2 取得了擁有權(quán)開始更新 Y,則 core1 對應(yīng)的緩存行需要設(shè)為 I 狀態(tài)(失效態(tài))。
輪番奪取擁有權(quán)不但帶來大量的 RFO 消息,而且如果某個線程需要讀此行數(shù)據(jù)時,L1 和 L2 緩存上可能都是失效數(shù)據(jù),所以就會從L3 緩存甚至是主存讀取數(shù)據(jù),很慢。
表面上 X 和 Y 都是被獨立線程操作的,而且兩操作之間也沒有任何關(guān)系。只不過它們共享了一個緩存行,但所有競爭沖突都是來源于共享。
3.3 對象占用的空間:
64位系統(tǒng)環(huán)境下,默認開啟壓縮-XX:+UseCompressedOops:
對象占用空間 =
對象頭(
Mark Word (32位JVM,4字節(jié))(64位JVM,8字節(jié))
+ Klass指針 (32位JVM,4字節(jié))(64位JVM,8字節(jié))(64位JVM && 默認開啟壓縮-XX:+UseCompressedOops,4字節(jié))
+ Array length (固定為4字節(jié),因為數(shù)組最大長度是int最大值)
?。?/p>
?。↘lass指針的大小根據(jù)是否開啟壓縮而定-XX:+UseCompressedOops)
+ 實例數(shù)據(jù)(
對象內(nèi)部的基本類型屬性:根據(jù)其大小而定比如 int=4字節(jié) 。byte 1;boolean 1;char 2;short 2;int 4;float 4;long 8;double 8
+ 對象內(nèi)部的引用類型屬性:只需要占用其 Klass指針部分即可( 即4字節(jié)或者8字節(jié))
?。?/p>
+ 對齊填充(
補齊為8字節(jié)的倍數(shù)
?。?/p>
3.4 偽共享的解決
偽共享常用解決方式1:
寫代碼手動填充。(目前好像都是對Long的填充)
先計算好對象占用的空間(對象頭+實例數(shù)據(jù)+對齊填充),然后再加幾個屬性填充成64字節(jié)甚至是128字節(jié)(看需求而定
偽共享常用解決方式2:
類名用@Contended注釋。
內(nèi)部的實現(xiàn)應(yīng)該是占用了128字節(jié)。
偽共享解決方式的應(yīng)用:
ConcurrentHashMap的CounterCell
浙公網(wǎng)安備 33010602011771號