CPU的亂序執行:
CPU在進行讀等待的同時執行指令,是CPU亂序的根源,這其實不是亂,而是提高效率。例如指令1去內存讀數據,因爲CPU與內存訪問速度相差100倍,如果指令2的執行過程不需要依賴指令1,那麼指令2可以先執行,亂序執行的本質是同時執行。Java對象的創建過程不是一個原子操作,極有可能出現指令重排序,下面通過Java對象創建的彙編碼講解。
// 源碼:
class T { int num = 8;}
T t = new T();
// 彙編碼:
0 new #2 <T> ---> new了一塊內存,對象屬性num賦初始值(0)
3 dup ---> 複製棧中的引用,供下面的invokespecial消耗
4 invokespecial #3 <T.<init>> ---> 執行構造方法,對象屬性num賦默認值(8),將堆中的對象地址與棧中的引用建立關聯
7 astore_1 ---> 將棧中的引用彈出賦值局部變量表的第一個位置,第0個位置是this
8 return
這也是DCL(Double Check Lock)單例必須要加上volatile關鍵字的原因,CPU層面使用內存屏障禁止指令重排序,通過在指令1和指令2之間插入內存屏障來禁止指令重排序,Inter通過原語lfence(load), sfence(save), mfence(mixed)
實現內存屏障,當然也可以使用總線鎖來解決。
- sfence:在sfence指令前的寫操作必須在sfence指令後的寫操作前完成;
- lfence:在lfence指令前的讀操作必須在lfence指令後的讀操作前完成;
- mfence:在mfence指令前的讀寫操作必須在mfence指令後的讀寫操作前完成;
- lock:原子指令,如x86上的
lock...
指令是一個Full Barrier,執行時會鎖住內存子系統來確保執行順序,甚至跨越多個CPU,這是硬件層次. - volatile:locl addl 0x0(exp),向exp寄存器中加0,主要是執行lock指令;
- sychronized:lock comxchg,通過自旋獲得鎖才能執行後面的操作;
// DCL單例
public class DCLInstance{
private static volatile DCLInstance instance = null;
private DCLInstance(){}
public static DCLInstance getInstance(){
if(instance==null){
sychronized(DCLINstance.class){
if(instance==null){
instance = new DCLINstance();
}
}
}
return instance;
}
}
Write Combining 合併寫技術:
Write Combining Buffer一般是4個字節,由於ALU速度太快,爲了提高寫效率,CPU在寫入L1時,寫入一個WC Buffer,當WC Buffer滿了之後,直接用WC寫入L2。
可以通過程序對合並寫技術進行驗證,如下所示程序,runCaseOne
中將7次寫入操作一次性執行,runCaseTwo
中將寫操作分爲兩組,每組4次寫操作,一共8次寫操作,但是runCaseTwo
的執行耗時卻比runCaseOne
要少。
public class WriteCombining {
private static final int ITERATIONS = Integer.MAX_VALUE;
private static final int ITEMS = 1 << 24;
private static final int MASK = ITEMS - 1;
private static final byte[] arrayA = new byte[ITEMS];
private static final byte[] arrayB = new byte[ITEMS];
private static final byte[] arrayC = new byte[ITEMS];
private static final byte[] arrayD = new byte[ITEMS];
private static final byte[] arrayE = new byte[ITEMS];
private static final byte[] arrayF = new byte[ITEMS];
public static long runCaseOne() {
long start = System.nanoTime();
int i = ITERATIONS;
while (--i != 0) {
int slot = i & MASK;
byte b = (byte) i;
arrayA[slot] = b;
arrayB[slot] = b;
arrayC[slot] = b;
arrayD[slot] = b;
arrayE[slot] = b;
arrayF[slot] = b;
}
return System.nanoTime() - start;
}
public static long runCaseTwo() {
long start = System.nanoTime();
int i = ITERATIONS;
while (--i != 0) {
int slot = i & MASK;
byte b = (byte) i;
arrayA[slot] = b;
arrayB[slot] = b;
arrayC[slot] = b;
}
i = ITERATIONS;
while (--i != 0) {
int slot = i & MASK;
byte b = (byte) i;
arrayD[slot] = b;
arrayE[slot] = b;
arrayF[slot] = b;
}
return System.nanoTime() - start;
}
public static void main(final String[] args) {
System.out.println("單次執行 (ms) = " + runCaseOne()/100_0000);
System.out.println("拆分兩次執行 (ms) = " + runCaseTwo()/100_0000);
}
}
// 輸出:
單次執行 (ms) = 4682
拆分兩次執行 (ms) = 4462