cpu偽共享問題
CPU內部也會有自己的緩存,內部的緩存單位是行,叫做緩存行。在多核環境下會出現CPU之間的內存同步問題(比如一個核加載了一份緩存,另外一個核也要用到同一份數據),如果每個核每次需要時都往內存中存取,這會帶來比較大的性能損耗,這個問題一般是通過MESI協議來解決的。
MESI協議中包含M、E、S、I四個狀態,分別的意思是:
- M(修改, Modified): 本地處理器已經修改緩存行, 即是臟行, 它的內容與內存中的內容不一樣. 并且此cache只有本地一個拷貝(專有).
- E(專有, Exclusive): 緩存行內容和內存中的一樣, 而且其它處理器都沒有這行數據
- S(共享, Shared): 緩存行內容和內存中的一樣, 有可能其它處理器也存在此緩存行的拷貝
- I(無效, Invalid): 緩存行失效, 不能使用

cpu在對緩存行進行了不同的操作后,在cpu緩存行中會記錄緩存的不同狀態。當一個核要對共享的數據進行寫操作時,需要給其他核發送RFO(REQUEST FOR OWNER)消息并把其他核的數據改成I態。這是一種比較消耗性能的操作。
cpu的偽共享問題本質是:幾個在邏輯上并不包含在同一個內存單元內的數據,由于被cpu加載在同一個緩存行當中,當在多線程環境下,被不同的cpu執行,導致緩存行失效而引起的大量的緩存命中率降低。
例如:當兩個線程分別對一個數組中的兩份數據進行寫操作,每個線程操作不同index上的數據,看上去,兩份數據之間是不存在同步問題的,但是,由于他們可能在同一個cpu緩存行當中,這就會使這一份緩存行出現大量的緩存失效,如前所述當一份線程更新時要給另一份線程發送RFO消息并把它的緩存失效掉。
解決這個問題的一個辦法是讓這個數組中不同index的數據在不同的緩存行:因為緩存行的大小是64個字節,那我們只要讓數組中沒份數據的大小大于64個字節,就可以保證他們在不同的緩存行當中,就能避免這樣的偽共享問題。
比如一個類當中原本只有一個long類型的屬性。這樣這個類型的對象只占了16個字節(java對象頭有8字節),如果這個類型被定義成一個長度為4的數組,這個數組的所有數據都可能在一個緩存行當中,就可能出現偽共享問題,那么這個時候,就可以采用補齊(padding)的辦法,在這個類型中加上public long a,b,c,d,e,f,g;這六個無用的屬性定義,使得這個類型的一個實例占用內存達到64字節,這樣這個類型的偽共享問題就得到了解決,在多線程當中對這個類型的數組進行寫操作就能避免偽共享問題。