volatile关键字详解

实现原理

//instance是volatile变量
instance = new Singleton();

转换为汇编代码:

0x01a3de1d: movb $0×0,0×1104800(%esi);
0x01a3de24: lock add1 $0×0,(%esp)

在有volatile变量修饰的共享变量进行写操作时会多出第二行代码。 lock前缀的指令在多核处理器中会引发两件事情:

  • 将当前处理器缓存行的数据写回到系统内存。 lock指令导致在执行指令期间,声言处理器的LOCK#信号。LOCK#信号使CPU在执行期间锁住总线,其他CPU无法访问主线也就不能访问系统内存。随着发展,处理器不锁总线而变成锁缓存,毕竟锁总线开销太大。缓存锁定后,会阻止同时修改两个以上处理器缓存的内存区域数据。
  • 这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效。 处理器能嗅探其他处理器访问系统内存和它们的内部缓存,如果一个处理器检测到其他处理器打算写内存地址,而这个地址处于共享状态,那么正在嗅探的处理器将使它的缓存行无效,下次访问时,强制执行缓存填充,从主内存读取数据。

内存语义

  • 当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。

特性

  • 可见性:对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。
  • 原子性:对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性。

复合操作的非原子性

  • 复合操作比如:
    • n ++;
    • n = n + 1;
public class Test {
    public volatile int inc = 0;
     
    public void increase() {
        inc++;//复合操作
    }     
    public static void main(String[] args) {
        final Test test = new Test();
        for(int i=0;i<10;i++){
            new Thread(){
                public void run() {
                    for(int j=0;j<1000;j++)
                        test.increase();
                };
            }.start();
        }    
        while(Thread.activeCount()>1)  //保证前面的线程都执行完
            Thread.yield();
        System.out.println(test.inc);
    }
}

如果inc具有原子性,那么最后得到的值应该是10000,但是事实上是小于10000的。 假如有一种情况:

inc主内存中为10,在线程1的inc ++操作中,先读取inc的原始值,此时线程1被阻塞了,然后线程2对变量进行了inc ++操作,而在此时线程1还没有进行写操作,那么此时inc就依旧是读取主内存的操作,所以依旧是10(本来应该是11)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值