1>防止指令重排
2>禁用工作内存缓冲区,直接使用主内存。
经典使用场景
场景1
public static Singleton getInstance() {
//第一次null检查
if (instance == null) {
synchronized (Singleton.class) { //1
//第二次null检查
if (instance == null) { //2
instance = new Singleton();//3
}
}
}
return instance;
}
如果不用volatile,则因为内存模型允许所谓的“无序写入”,可能导致失败 。某个线程可能会获得一个未完全初始化的实例。
场景2
private vola
tile 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这个变量的值。一旦第一个线程看到了第二个线程的值的变化,就会马上停止循环。
运行结果如下:
说明经过了两个循环的时间,线程就读到了另外一个线程变化的值。对照下面的时间延迟表,我们来计算下:
平均执行一行简单代码要执行5个指令。如上执行一个指令需要1ns。每次循环执行2行代码,从运行结果来看共执行了2次。共5_1_2*2=20ns。实际数据应该不是如此,而且是变化的。但是应该都是ns级别的。
要避免的误区](https://img-blog.csdnimg.cn/img_convert/e3ce33f5d860603890fc2e97cd4ce4e7.png)
平均执行一行简单代码要执行5个指令。如上执行一个指令需要1ns。每次循环执行2行代码,从运行结果来看共执行了2次。共5_1_2*2=20ns。实际数据应该不是如此,而且是变化的。但是应该都是ns级别的。