<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          真實字節(jié)二面:什么是偽共享?

          共 2149字,需瀏覽 5分鐘

           ·

          2021-03-02 11:35

          這個問題來自最近一個朋友字節(jié)面試碰到的,最后他也成功拿到了字節(jié)offer,這個問題我想可能挺多人不太清楚,所以想拿出來單獨說一說。

          好了,讓我們進入正題。

          什么是偽共享

          首先大家都知道,隨著CPU和內存的發(fā)展速度差異的問題,導致CPU的速度遠遠快于內存,所以一般現(xiàn)在的CPU都加入了高速緩存,就是常說的解決不同硬件之間的性能差異問題。

          這樣的話,很簡單的道理,加入了緩存,就必然會導致緩存一致性的問題,由此,又引入了緩存一致性協(xié)議。(如果你不知道,建議去百度一下,這里不做展開)

          CPU緩存,顧名思義,越貼近CPU的緩存速度越快,容量越小,造價成本也越高,而高速緩存一般可以分為L1、L2、L3三級緩存,按照性能的劃分:L1>L2>L3。

          而事實上,數(shù)據(jù)在緩存內部都是按照來存儲的,這就叫做緩存行。緩存行一般都是2的整數(shù)冪個字節(jié),一般來說范圍在32-256個字節(jié)之間,現(xiàn)在最為常見的緩存行的大小在64個字節(jié)。

          所以,按照這個存儲方式,緩存中的數(shù)據(jù)并不是一個個單獨的變量的存儲方式,而是多個變量會放到一行中。

          我們常說的一個例子就是數(shù)組和鏈表,數(shù)組的內存地址是連續(xù)的,當我們去讀取數(shù)組中的元素時,CPU會把數(shù)組中后續(xù)的若干個元素也加載到緩存中,以此提高效率,但是鏈表則不會,也就是說,內存地址連續(xù)的變量才有可能被放到一個緩存行中

          在多個線程并發(fā)修改一個緩存行中的多個變量時,由于只能同時有一個線程去操作緩存行,將會導致性能的下降,這個問題就稱之為偽共享。

          為什么只有一個線程能去操作?我們舉個實際的栗子來說明這種情況:

          假設緩存中有x,y兩個變量,他們同時已經在不同的三級緩存之中。

          這時有兩個線程A和B同時去修改位于Core1和Core2的變量xy

          如果線程A去修改Core1的緩存中的x變量,由于緩存一致性協(xié)議,Core2中對應的緩存了x,y變量的緩存行將會失效,他會被強制從主內存中重新去加載變量。

          這樣的話,頻繁的訪問主內存,緩存基本都失效了,將會導致性能的下降,這就是偽共享的問題。

          如何避免?

          既然已經知道了什么是偽共享,那么怎么避免這種情況的發(fā)生?

          改變行存儲的方式?想都別想了。

          剩下可行的方法就是填充,如果這一行只有我這一個數(shù)據(jù)那不就好了嗎?

          確實就是這樣,解決方式通常有以下兩種。

          字節(jié)填充

          在JDK8之前,可以通過填充字節(jié)的方式來避免偽共享的問題,如下代碼所示:

          自定義填充

          一般而言,緩存行有64字節(jié),我們知道一個long是8個字節(jié),填充5個long之后,一共就是48個字節(jié)。

          而 Java 中對象頭在32位系統(tǒng)下占用8個字節(jié),64位系統(tǒng)下占用16個字節(jié),這樣填充5個long型即可填滿64字節(jié),也就是一個緩存行。

          @Contented注解

          JDK8以及之后的版本 Java 提供了sun.misc.Contended 注解,通過@Contented注解就可以解決偽共享的問題。

          注解方式

          使用@Contented注解后會增加128字節(jié)的padding,并且需要開啟-XX:-RestrictContended選項后才能生效。

          所以,通過以上兩種方式你會發(fā)現(xiàn),對象頭大小和緩存行的大小都和操作系統(tǒng)位數(shù)有關,JDK的注解幫你解決了這個問題,所以推薦盡量使用注解的方式來實現(xiàn)。

          雖然解決了偽共享問題,但是這種填充的方式也浪費了緩存資源,明明只有8B的大小,硬是使用了64B緩存空間,造成了緩存資源的浪費。

          而且我們知道,緩存又小又貴,時間和空間的取舍要自己酌情考慮。

          實際應用

          在Java中提供了多個原子變量的操作類,就是比如AtomicLong、AtomicInteger這些,通過CAS的方式去更新變量,但是失敗會無限自旋嘗試,導致CPU資源的浪費。

          為了解決高并發(fā)下的這個缺點,JDK8中新增了LongAdder類,他的使用就是對解決偽共享的實際應用。

          LongAdder繼承自Striped64,內部維護了一個Cell數(shù)組,核心思想就是把單個變量的競爭拆分,多線程下如果一個Cell競爭失敗,轉而去其他Cell再次CAS重試。

          Striped64成員變量

          解決偽共享的真正的核心就在Cell數(shù)組,可以看到,Cell數(shù)組使用了Contented注解。

          在上面我們提到數(shù)組的內存地址都是連續(xù)的,所以數(shù)組內的元素經常會被放入一個緩存行,這樣的話就會帶來偽共享的問題,影響性能。

          這里使用Contented進行填充,就避免了偽共享的問題,使得數(shù)組中的元素不再共享一個緩存行。

          解決偽共享

          好了,今天的內容就到這里,我是艾小仙,我的slogan還沒想好,但是我們下次見。

          ·················END·················



          往期推薦

          關于MVCC,我之前寫錯了,這次我改好了!

          長篇連載,人生30年(一):你的編程能力從什么時候開始突飛猛進?

          好久沒更新,新年第一篇!

          好了,我攤牌了,B站,牛逼!

          長篇連載,人生30年(二):職場菜鳥被開除

          《我想進大廠》之Zookeeper奪命連環(huán)9問

          瀏覽 60
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  αv天堂αv电影亚洲ωa | 区一区二免费视频 | 国产精品77 | 麻豆传媒兔子先生 | 日韩无码黄片 |