volatile实现原理

可见性实现

​​​​​​​

 

可见性实现主要借助汇编lock前缀指令来实现。lock前缀会发出LOCK#信号指令执行分为两种情况:

变量所在内存缓存行(cache line)对齐:不加锁,直接修改主存值

变量所在内存缓存行(cache line)没有对齐:锁缓存行,修改主存值

MESI协议:当CPU写数据时,如果发现操作的变量是共享变量,即在其它 CPU 中也存在该变量的副本,会发出信号通知其它CPU将该内存变量的缓存行设置为无效。其他CPU通过总线嗅探协议,不停在嗅探总线上发生的数据交换,跟踪其他缓存在做什么,它们以此来使自己的缓存保持同步。

由于MESI协议需要不断的从总线嗅探和CAS循环,无效的交互导致总线带宽达到峰值,因此不推荐大量使用volatile关键字。

有序性实现

as-if-serial

happens-before规则:

1、程序次序原则

2、锁原则

3、volatile

4、线程启动原则

5、线程中断原则

6、线程终止原则

7、对象终结原则

8、对象终结原则

volatile的有序性主要基于以上原则来实现。

使用内存屏障禁止指令重排序:

内存屏障主要分为以下4类:

内存屏障插入策略:

在每个volatile写操作的前面插入一个StoreStore屏障。
在每个volatile写操作的后面插入一个StoreLoad屏障。
在每个volatile读操作的后面插入一个LoadLoad屏障。
在每个volatile读操作的后面插入一个LoadStore屏障。

那么volatile是怎么实现内存屏障的呢?

volatile是通过lock前缀指令实现内存屏障来禁止指令重排序的,lock前缀指令锁的是总线或者缓存行。

为什么不能保证原子性

i++的字节码如下:

public class Test {
    public static void main(String[] args) {
        MyData myData = new MyData();
        myData.incre();
        System.out.println(myData.getI());
    }
}

class MyData {
    volatile int i = 0;

    public void incre() {
        i++;
    }

    public int getI() {
        return i;
    }
}

javap -c MyData后查看字节码指令:

class cn.com.gome.scot.alamein.share.web.app.MyData {
  volatile int i;

  cn.com.gome.scot.alamein.share.web.app.MyData();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: iconst_0
       6: putfield      #2                  // Field i:I
       9: return

  public void incre();
    Code:
       0: aload_0
       1: dup
       2: getfield      #2                  // Field i:I
       5: iconst_1
       6: iadd
       7: putfield      #2                  // Field i:I
      10: return

  public int getI();
    Code:
       0: aload_0
       1: getfield      #2                  // Field i:I
       4: ireturn
}

可见,i++被编译程4条指令:

2: getfield      #2                  // Field i:I     从主内存拿到原始i
5: iconst_1
6: iadd                                            // 执行i++进行i加1操作
7: putfield      #2                  // Field i:I  // 把累加后的值写回主内存

三个线程同时通过getfield命令,拿到主存中的n值,然后三个线程,各自在自己的工作内存中进行加1操作,但他们并发进行 iadd 命令的时候,因为只能一个进行写,所以其它操作会被挂起,假设1线程,先进行了写操作,在写完后,由于volatile的可见性,应该需要告诉其它两个线程,主内存的值已经被修改了,但是因为太快了,其它两个线程,陆续执行 iadd命令,进行写入操作,这就造成了其他线程没有接受到主内存n的改变,从而覆盖了原来的值,出现写丢失

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值