其他:volatile的原理解析

在看AtomicInteger代码的时候,看到了它的原始是这么定义的    private volatile int value;,在unsafe类中有这几种原生的方法

  var5 = this.getIntVolatile(var1, var2);
  var6 = this.getLongVolatile(var1, var2);
  var5 = this.getObjectVolatile(var1, var2);


为什么要这么定义,getXXVolatile方法又代表什么呢,基本解释就是:支持 volatile load 语义,那这句话又是什么意思呢?下边是摘自网上的例子

//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++这种复合操作不具有原子性。

两种应用场景:

将 volatile 变量作为状态标志使用

volatile boolean shutdownRequested;
 
public void shutdown() { shutdownRequested = true; }
 
public void doWork() { 
    while (!shutdownRequested) { 
        // do stuff
    }
}


上面的这种场景就很适合使用 volatile 变量来控制并发,即当 shutdown () 方法调用时,能保证所有线程中执行的 doWork () 方法都立即停下来。

例如常见的促销活动 “秒杀”,可以用 volatile 来修饰 “是否售罄” 字段,从而保证在并发下,能正确的处理商品是否售罄。

然而,使用 synchronized 块编写循环要比使用上面基于 volatile 状态标志的实现麻烦很多。由于 volatile 简化了编码,并且状态标志并不依赖于程序内任何其他状态,因此此处非常适合使用 volatile。

这种类型的状态标记的一个公共特性是:通常只有一种状态转换;shutdownRequested 标志从 false 转换为 true,然后程序停止。

单例模式 double check
为什么要加 volatile 关键字,就是为了保证 instance 的初始化完成之后才会被使用,以免报错。如果不使用,可能会出现,线程 A 先 new 了一个对象 分配了内存地址,但是初始化对象的工作没有完成。此时线程 B 进来,instance 不为空。线程 B 持有 instance 然后使用的时候报错。

class Singleton{
    private volatile static Singleton instance = null;
     
    private Singleton() {}
     
    public static Singleton getInstance() {
        if(instance==null) {
            synchronized (Singleton.class) {
                if(instance==null)
                    instance = new Singleton();
            }
        }
        return instance;
    }
}


性能考虑
使用 volatile 变量的主要原因是其简易性:在某些情形下,使用 volatile 变量要比使用相应的锁简单得多。使用 volatile 变量次要原因是其性能:某些情况下,volatile 变量同步机制的性能要优于锁。

由于 volatile 变量中并不存在任何锁,因而访问这个变量并不会造成正在执行的线程进入阻塞态。因此,volatile 变量是一种比 synchronized 块更轻量级的同步机制。一句话概括,锁可以保证可见性和原子性,而 volatile 变量只能保证可见性。

在目前大多数的处理器架构上,volatile 读操作开销非常低 —— 几乎和非 volatile 读操作一样。而 volatile 写操作的开销要比非 volatile 写操作多很多,因为要保证可见性需要实现内存界定(Memory Fence),即便如此,volatile 的总开销仍然要比锁获取低。

volatile 操作不会像锁一样造成阻塞,因此,在能够安全使用 volatile 的情况下,volatile 可以提供一些优于锁的可伸缩特性。如果读操作的次数要远远超过写操作,与锁相比,volatile 变量通常能够减少同步的性能开销。

总结一下文章开始提到的问题:unsafe里边的CAS保证了原型执行,volatile保证里可见性,一个是原语开销低,另外一个开销也低,这就造就了整个的数据的安全,但是注意这些在分布式环境中依然是没有意义的。

摘自以下文章:

volatile原理

Java 关键字 - volatile 关键字

彻底理解volatile

Java volatile Example

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值