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