as-if-serial原则:不管怎么重排序(编译器和处理器为了提高并行度),(单线
程)程序的执行结果不能被改变
为了提高cpu执行效率,编译器和处理器不会对存在数据依赖关系的操作做重排序,因为
这种重排序会改变执行结果。但是,如果操作之间不存在数据依赖关系,这些操作就可能被编译
器和处理器重排序。一句话概括,不会改变执行结果会进行指令重拍。反之不会指令 重拍。
计算圆的周长,A,B可以发生指令重拍,因为A,B发生重拍不会影响结果
double pi = 3.14; // A
double r = 5.0; // B
double area = 2*pi * r ;
happens-before原则:
1.程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操
作;
2.锁定规则:一个unLock操作先行发生于后面对同一个锁的lock操作;
3.volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作;
4.传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A
先行发生于操作C
主要是2和3,lock规定和volatile规定
happens-before原则是JMM中非常重要的原则,它是判断数据是否存在竞争、线程是否安全的
主要依据,保证了多线程环境下的可见性
cpu多级缓存架构:
cpu,寄存器,pc计数器,cache,LRU(逻辑运算器),由于cpu和主存存在运算级别的差异,大概是100:1的速度,所以增加了三级缓存以提高cpu运算效率,
这种三级缓存设计架构有些弊端:比如cpu1执行8大原子操作,比如+3操作,cpu2执行+5操作,如果cpu1执行完写入主存中,但是cpu2没有来的及读取到主存的值,导致最后读取到+3的结果,
而不是+8的最终正确结果,如何确保这种架构得到正确结果呢?
后续提出了一个MESI机制,也就是缓存一致性协议
M:modify E:独占 S:share i:invalid
已修改Modified (M)
缓存行是脏的(dirty),与主存的值不同。如果别的CPU内核要读主存这块数据,该缓存行必
须回写到主存,状态变为共享(S).
独占Exclusive (E)
缓存行只在当前缓存中,但是干净的--缓存数据同于主存数据。当别的缓存读取它时,状态变为
共享;当前写数据时,变为已修改状态。
共享Shared (S)
缓存行也存在于其它缓存中且是未修改的。缓存行可以在任意时刻抛弃。
无效Invalid (I)
缓存行是无效的
也就是cpu1操作完8大原子操作后,立马会通过总线嗅探机制,把数据写会主从,从而cpu2拿到正确的结果,这里还有一个总线裁判机制,也就是cpu1和cpu2不能对修改后的数据同时写入主从中。
伪共享的问题
如果多个核的线程在操作同一个缓存行中的不同变量数据,那么就会出现频繁的缓存失效,即使
在代码层面看这两个线程操作的数据之间完全没有关系。这种不合理的资源竞争情况就是伪共享
查看缓存行大小:64byte
cat /proc/cpuinfo
避免伪共享方案
class disp {
volatile long a
//避免伪共享: 缓存行填充
long p1, p2, p3, p4, p5, p6, p7;
volatile long b;
}
伪共享案例:两个线程处于两个cpu内核上,操作同一个缓存行中的不同变量数据,加上volatile执行效率2548ms,因为加了lock锁,,所以效率低,去掉volatile,执行结果42ms,如果在两个变量之间加上7个long,
long p1, p2, p3, p4, p5, p6, p7;
那么执行效率得到提高51ms,还是低于原生态的执行效率,应为底层还是基于MESI机制,
jdk8只需要加上一个注解,既可以自动往后补long,确保不再一个缓冲行里面执行,避免伪共享问题出现,不加锁提高效率的方法。
class Pointer { // 避免伪共享: @Contended + jvm参数:-XX:-RestrictContended jdk8支持 @Contended long x; //避免伪共享: 缓存行填充 long y; }
private static void testPointer(Pointer pointer) throws InterruptedException { long start = System.currentTimeMillis(); Thread t1 = new Thread(() -> { for (int i = 0; i < 100000000; i++) { pointer.x++; } }); Thread t2 = new Thread(() -> { for (int i = 0; i < 100000000; i++) { pointer.y++; } });
DCL为什么要使用volatile
if (object== null) { synchronized (a.class) { if (object== null) {}}}
return myInstance; 对象初始化3大步骤: // 1. 开辟一片内存空间 // 3. myInstance指向内存空间的地址 // 2. 对象初始化 假设第一个线程进来,按照1,2,3步骤执行,但此时2,3发生指令重拍,让对象指向先执行,那么 该对象的值就不为空,然后第二个线程进来,判断对象不为空,那么就拿到第一个线程的半初始状态的值,这显然不对
如何禁止指令重拍:加上volatile