写点什么

volatile 关键字的原理和要避免的误区

  • 2021 年 11 月 11 日
  • 本文字数:1257 字

    阅读完需:约 4 分钟

}


如果不用 volatile,则因为内存模型允许所谓的“无序写入”,可能导致失败 。某个线程可能会获得一个未完全初始化的实例。


场景 2


private volatile int value;


//读操作,没有 synchronized,提高性能


public int getValue() {


return value;


}


//写操作,必须 synchronized。因为 x++不是原子操作


public synchronized int increment() {


return value++;


}


这段代码,可实现一个线程间安全的计数器。因为加了 valatile 关键字。每次线程都能取到最新值做加减。


要避免的误区


在代码评审的时候看到 volatile 被滥用的情况。说说我个人的看法:很少变化,对时间不是特别敏感的情况下不建议用 volatile 关键字。


举个例子:从公司的配置中心取到一个配置数据。不建议用 volatile。


一般来说配置中心的架构是下面这个样子



一条数据从用户变更到集中存储的配置中心,配置中心下发到真正使用的机器上,之前公司是要经过 90s(客户端 90s 为周期定时去配置中心取最新数据)。


加了 volatile 关键字在这种场景只是能更快的看到这个最新值而已。下面我们来测试下这个【更快】有多久。


public class VolatileTest {


private boolean endRun = false;


@Test


public void noVolatile() throws Exception {


Runnable r1 = new Runnable() {


public void run() {


int i = 0;


while (!endRun) {


System.out.println("I am still running" + i++);


}


}


};


Runnable r2 = new Runnable() {


public void run() {


endRun = true;


}


};


new Thread(r1).start();


new Thread(r2).start();


Thread.sleep(9000);


System.out.println("end run");


}


}


这个代码里,在第一个线程使用 endRun 这个变量。第二个线程去改变 endRun 这个变量的值。一旦第一个线程看到了第二个线程的值的变化,就会马上停止循环。


运行结果如下:


![volatile 关键字的原理和要避免的误区](https://img-blog.csdnimg.cn/img_convert/fdfc4838c77f956fe4a9dcaedb5d87e


【一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


b.png)


说明经过了两个循环的时间,线程就读到了另外一个线程变化的值。对照下面的时间延迟表,我们来计算下:



平均执行一行简单代码要执行 5 个指令。如上执行一个指令需要 1ns。每次循环执行 2 行代码,从运行结果来看共执行了 2 次。共 5_1_2*2=20ns。实际数据应该不是如此,而且是变化的。但是应该都是 ns 级别的。


相比较 90s 的可见性延迟,ns 级别可以忽略不计。


再看看为了早 ns 级看到结果,所花费的开销:volatile 关键字本质是让 L1 缓存、L2 缓存这种 cpu 缓存失效了,直接主存访问。如果要访问的字段在 L1 缓存里,从配置中心取的数据 1 天变化一次。以字段放在 L2 缓存为例。加了 volatile 关键字,访问时间要从 4ns 上升到 100ns,如果这个变量每个请求都要访问,每秒 QPS 是 1000。则 1 天为了取这个数据将多花 1_24_3600_1000_(100-4)ns 约等于 8300ms。


相比获得的收益来讲,代价要大出好几个数量级。但是本身的时间开销本来就很小,坦白说一般的系统一天多花个 8.3s 也是可以接受的。但是这样的变量多了,也是个不小的负担。而且这个负担会随着系统压力增加而加重。

评论

发布
暂无评论
volatile关键字的原理和要避免的误区