当CPU得到的多个指令间没有依赖关系,那么CPU会在等待某条指令操作时优先执行另一条指令,上图有个很好的例子,烧开水的时候,人可以去洗茶杯茶壶,这样就可以提高喝茶的整体效率了~
但是,当在多线程场景中,CPU乱序执行可能就会出现问题
这里简单快速的解释一下(主要跟JVM有关)
1、由于JAVA创建对象时,有个中间态,处于中间态时,对象并没有完成全部的初始化。
2、如果此时,在中间态时,发生了CPU乱序执行,初始化对象指令还未执行,对象就被另一个线程使用,就会导致拿到没有初始化完成的对象。
(DCL单例就是解决这个问题,所以DCL单例必须使用volatile修饰对象, volatile可以禁止重排序,并且线程透明)
禁止乱序执行:
CPU层面上,要如何禁止指令的重排序呢?答案就是内存屏障。,对某部分内存做操作前后做出内存屏障,屏障前后指令不可乱序执行。
Intel CPU可以使用三种原语( lfence sfence mfence ),或者Lock指令来实现。
1、lfence -> loadfence 在lfence指令前的读操作必须在lfence指令后的读操作前执行,sfence -> storefence 在sfence指令前的写操作必须在sfence指令后的写操作前执行, mfence -> mixedfence 在mfence指令前的读写操作必须在mfence指令后的读写操作前执行
2、Lock指令比较狠,它属于X86 CPU指令,它会直接锁住内存总线(Memory BUS)
JVM层面上,JVM规范了他的内存屏障实现
1、loadload屏障 loadload屏障前的load1指令必须先于loadload屏障后的load2指令前完成,不可互换
2、storestore屏障 。。。。。。类推
3、loadstore屏障 loadstore屏障前的load1指令必须先于loadload屏障后的store2指令前完成,不可互换
4、storeload屏障 。。。。。。类推
举个栗子:JVM对volatile的实现,保证了可见性和禁止乱序
store1
storestore (等前面的store1写指令完成后才能开始volatile的写指令)
volatile写指令
storeload (等前面的volatile写指定完成后才能做后面的读指令)
load1
loadload (等前面的load1写指令完成后才能开始volatile的读指令 )
volatile读指令
loadstore (等前面的volatile读指令完成后才能做后面的写指令)
JVM同时也规定了指令重排序的必须遵守的8条规则,称之为Happens-Before规则(这个知道就好,不用细扣)
Happens-Before的规则包括:
程序顺序规则
锁定规则
volatile变量规则
线程启动规则
线程结束规则
中断规则
终结器规则
传递性规则
as-if-serial:不管硬件什么顺序,单线程执行的结果不变,看上去好像是serial顺序执行
SingleThreadPool 可以实现单线程的队列任务