CPU每次讀取并不是一個字節(jié)一個字節(jié)的讀取,它會一次讀取一塊內(nèi)容,這“塊”稱之為CPU的緩存行(CPU每次訪問主存會很慢;CPU的高速緩存L1,L2,L3;L1,L2是每個CPU Core獨有的,L3是所有Core共享的。我們可以把緩存行理解為高速緩存的組成最小單元)。最常見的緩存行大小是64byte(每次都會讀取64字節(jié)大?。?。

CPU 個緩存 及 主存
當CPU從主內(nèi)存中讀取一個變量的時候它會把相鄰的數(shù)據(jù)都一次性的加載到緩存中。當訪問相鄰數(shù)據(jù)的時候就不需要再到主存中讀取了,直接從緩存中獲取,提高執(zhí)行效率。
private static class CacheLineData {
private volatile long d1 = 0L;
private volatile long d2 = 0L;
private volatile long d3 = 0L;
private volatile long d4 = 0L;
}
在多核core環(huán)境下,當有多個線程訪問如上變量d1,d2,d3,d4,每個線程都運行在不同的core中
private static CacheLineData cacheLine = new CacheLineData() ;
private static int n = 1000000 ;
public static void main(String[] args) throws Exception {
int loop = 4 ;
Thread[] ts = new Thread[loop] ;
ts[0] = new Thread(() -> {
for (int m = 0; m < n; m++) {
cacheLine.d1 = m ;
}
}) ;
ts[1] = new Thread(() -> {
for (int m = 0; m < n; m++) {
cacheLine.d2 = m ;
}
}) ;
ts[2] = new Thread(() -> {
for (int m = 0; m < n; m++) {
cacheLine.d3 = m ;
}
}) ;
ts[3] = new Thread(() -> {
for (int m = 0; m < n; m++) {
cacheLine.d4 = m ;
}
}) ;
long start = System.currentTimeMillis() ;
for (int i = 0; i < loop; i++) {
ts[i].start() ;
}
for (int i = 0; i < loop; i++) {
ts[i].join() ;
}
System.out.println("耗時:" + (System.currentTimeMillis() - start) + " ms") ;
}
如上代碼有4個線程分別訪問d1,d2,d3,d4。如何產(chǎn)生偽共享的呢?當線程1修改了d1變量后,其它線程的緩存行多會作廢并重新從主存中獲取數(shù)據(jù)(volatile 修飾的變量,在多線程情況下訪問時當有一個線程修改了這個變量,那么會通過消息總線通知其他線程該變量已經(jīng)修改并將其置為invalid狀態(tài),再使用時必須重新從主存中獲取)。
如上示例代碼運行結(jié)果:
50ms左右
當把volatile修飾符去掉后的運行結(jié)果:
private static class CacheLineData {
private long d1 = 0L;
private long d2 = 0L;
private long d3 = 0L;
private long d4 = 0L;
}
7ms左右
方法1、接下來我們可以通過填充的方法來使得每個變量都處在不同的緩存行中。一個long占8個字節(jié)(我們這里按照緩存行64個字節(jié)來算)
private static class CacheLineData {
private volatile long k0, k1, k2, k3, k4, k5, k6, k7 ;
private volatile long d1 = 0L;
private volatile long n0, n1, n2, n3, n4, n5, n6, n7 ;
private volatile long d2 = 0L;
private volatile long z0, z1, z2, z3, z4, z5, z6, z7 ;
private volatile long d3 = 0L;
private volatile long a0, a1, a2, a3, a4, a5, a6, a7 ;
private volatile long d4 = 0L;
}
執(zhí)行結(jié)果:
11ms左右
方法2:在JAVA8中可以通過 @Contended注解
Contended注解可以用于類型上和屬性上,加上這個注解之后虛擬機會自動進行填充,從而避免偽共享。
private static class CacheLineData {
@Contended
private volatile long d1 = 0L;
@Contended
private volatile long d2 = 0L;
@Contended
private volatile long d3 = 0L;
@Contended
private volatile long d4 = 0L;
}
要使Contended注解生效需要啟動jvm時加入如下參數(shù):
-XX:-RestrictContended
執(zhí)行結(jié)果:
13ms左右
關(guān)于變量在其它core中是怎么失效的是通過MESI協(xié)議來完成的。