java中什么是 伪共享_【Java】聊聊多线程中的伪共享现象
首頁
專欄
java
文章詳情
0
聊聊多線程中的偽共享現象
小強大人發布于 1 月 27 日
什么是偽共享?
講偽共享之前,讓我們先乘坐時光機,回到大學課堂,來重溫下計算機組成原理的基礎知識。我們知道,CPU和內存的運行速度相差很大,為了解決這個問題,在CPU和內存之間會加一級或多級高速緩存(Cache)。這個Cache一般是集成在CPU內部的,所以也叫CPU Cache。下圖是一個兩級Cache的CPU-Cache-內存架構。
數據在Cache中是按行存儲的,其中每一行稱為一個Cache行,如下圖所示。它是CPU與內存數據交換的基本單位。Cache行的大小一般為2的冪次字節數。
CPU-Cache-內存架構的工作原理是這樣的:當CPU訪問某個變量時,首先會從CPU Cache里查看是否有該變量,如果有直接獲取并返回,否則從內存中獲取,然后把該變量所在內存區域的一個Cache行大小的內存數據復制到Cache中,這就是我們所說的局部性原理。由于存放到Cache行的不是單個變量,而是一個內存塊數據,所以會出現多個變量放在一個Cache行中。當多個線程同時修改同一個緩存行里面的不同變量時,由于同一時刻只允許一個線程操作緩存行,一個線程成功獲取緩存行的修改權時,其他線程會互斥等待,并且由于緩存一致性協議,其他線程相應的緩存行會失效,需要重新從內存獲取數據,這無疑耗費了更多時間。所以相比把每個變量放在不同的緩存行,性能反而有所下降,這就是偽共享現象。
如下圖所示,變量x,y放在同一個緩存行中,線程1操作緩存行的x變量,線程2操作y變量。
如何避免偽共享呢?
在JDK1.8之前,通常是通過字節填充的方式。什么意思呢?就是用到一個變量時,補充額外的若干輔助變量,使得這些變量剛好填充滿一個緩存行,這樣就避免了多個變量存放在一個緩存行中。具體看下面示例代碼:
static final class PaddedLongField {
public volatile long value = 0L;
public long p1,p2,p3,p4,p5,p6;
}
假如CPU Cache行大小為64字節,那么我們這里填充了6個long型的變量,每個long型變量占8個字節,加上value一共7*8=56字節,另外,別忘了PaddedLongField是一個類對象,對象頭還要占用8個字節,所以一個PaddedLongField對象占用64個字節,剛好填充滿一個緩存行。
在JDK1.8之后,提供了一個@sun.misc.Contended注解,用來解決偽共享問題。此時,我們上面的代碼就可以簡化了:
@sun.misc.Contended
static final class PaddedLongField {
public volatile long value = 0L;
}
@Contended注解不僅可以修飾類,也可以修飾變量:
JUC源碼里很多使用這個注解的,比如Thread類里threadLocalRandom相關的變量:
再比如LongAdder內部用到的Cell也用了這個注解:
再比如ForkJoinPool類上面也修飾了:
最后需要注意下,@Contended注解默認只用于Java核心類,比如rt.jar下的類。如果我們應用程序中想使用這個注解,需要添加一個JVM參數:-XX:-RestrictContended,填充的默認寬度為128字節,若需自定義寬度則可以用另一個參數:-XX:ContendedPaddingWidth=xxx
本文就到這里啦,若這篇文章對你有所幫助的話,點個贊再走叭!謝謝支持!
參考資料:
《Java并發編程之美》
java
閱讀 53更新于 1 月 27 日
贊收藏
分享
本作品系原創,采用《署名-非商業性使用-禁止演繹 4.0 國際》許可協議
小強大人
9聲望
1粉絲
關注作者
0 條評論
得票時間
提交評論
小強大人
9聲望
1粉絲
關注作者
宣傳欄
▲
什么是偽共享?
講偽共享之前,讓我們先乘坐時光機,回到大學課堂,來重溫下計算機組成原理的基礎知識。我們知道,CPU和內存的運行速度相差很大,為了解決這個問題,在CPU和內存之間會加一級或多級高速緩存(Cache)。這個Cache一般是集成在CPU內部的,所以也叫CPU Cache。下圖是一個兩級Cache的CPU-Cache-內存架構。
數據在Cache中是按行存儲的,其中每一行稱為一個Cache行,如下圖所示。它是CPU與內存數據交換的基本單位。Cache行的大小一般為2的冪次字節數。
CPU-Cache-內存架構的工作原理是這樣的:當CPU訪問某個變量時,首先會從CPU Cache里查看是否有該變量,如果有直接獲取并返回,否則從內存中獲取,然后把該變量所在內存區域的一個Cache行大小的內存數據復制到Cache中,這就是我們所說的局部性原理。由于存放到Cache行的不是單個變量,而是一個內存塊數據,所以會出現多個變量放在一個Cache行中。當多個線程同時修改同一個緩存行里面的不同變量時,由于同一時刻只允許一個線程操作緩存行,一個線程成功獲取緩存行的修改權時,其他線程會互斥等待,并且由于緩存一致性協議,其他線程相應的緩存行會失效,需要重新從內存獲取數據,這無疑耗費了更多時間。所以相比把每個變量放在不同的緩存行,性能反而有所下降,這就是偽共享現象。
如下圖所示,變量x,y放在同一個緩存行中,線程1操作緩存行的x變量,線程2操作y變量。
如何避免偽共享呢?
在JDK1.8之前,通常是通過字節填充的方式。什么意思呢?就是用到一個變量時,補充額外的若干輔助變量,使得這些變量剛好填充滿一個緩存行,這樣就避免了多個變量存放在一個緩存行中。具體看下面示例代碼:
static final class PaddedLongField {
public volatile long value = 0L;
public long p1,p2,p3,p4,p5,p6;
}
假如CPU Cache行大小為64字節,那么我們這里填充了6個long型的變量,每個long型變量占8個字節,加上value一共7*8=56字節,另外,別忘了PaddedLongField是一個類對象,對象頭還要占用8個字節,所以一個PaddedLongField對象占用64個字節,剛好填充滿一個緩存行。
在JDK1.8之后,提供了一個@sun.misc.Contended注解,用來解決偽共享問題。此時,我們上面的代碼就可以簡化了:
@sun.misc.Contended
static final class PaddedLongField {
public volatile long value = 0L;
}
@Contended注解不僅可以修飾類,也可以修飾變量:
JUC源碼里很多使用這個注解的,比如Thread類里threadLocalRandom相關的變量:
再比如LongAdder內部用到的Cell也用了這個注解:
再比如ForkJoinPool類上面也修飾了:
最后需要注意下,@Contended注解默認只用于Java核心類,比如rt.jar下的類。如果我們應用程序中想使用這個注解,需要添加一個JVM參數:-XX:-RestrictContended,填充的默認寬度為128字節,若需自定義寬度則可以用另一個參數:-XX:ContendedPaddingWidth=xxx
本文就到這里啦,若這篇文章對你有所幫助的話,點個贊再走叭!謝謝支持!
參考資料:
《Java并發編程之美》
總結
以上是生活随笔為你收集整理的java中什么是 伪共享_【Java】聊聊多线程中的伪共享现象的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: html怎么无损插入背景音乐,HTML插
- 下一篇: 自定义键盘组件_一文读懂!iOS系统组件