并发三大特性2-有序性

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值