volatile关键字

介绍

volatile关键字,java给出的线程安全的轻量级实现
1、保证可见性,用volatile修饰的变量每次都是从内存中读取
2、禁止指令重排序,用volatile修饰的变量禁止指令重排优化
3、不保证原子性,要保证原子性的话需要使用synchronize关键字或者concurrent包下的原子类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Singleton {
private static volatile Singleton instance;

private Singleton(){}
// DCL
public static Singleton getInstance(){
if(instance ==null){ //第一次检查
synchronized (Singleton.class){
if(instance == null){ //第二次检查
instance = new Singleton();
}
}
}
return instance;
}
}

以上是常见的双重校验的单例模式
instance = new Singleton(),分为三步:
1、给instance分配内存,
2、调用它的构造方法来初始化对象,
3、将instance对象指向初始分配的内存空间(这一步执行了instance就非null)

在这三步中,第二步和第三步不存在数据依赖,如果虚拟机指令重排优化,就会存在其他线程访问的时候instance为非null的情况,然后访问就会出错
这个版本的双重校验单例模式,使用了volatile关键字去禁止指令重排序来保证线程安全。

性能

当对非 volatile 变量进行读写的时候,每个线程先从内存拷贝变量到 CPU 缓存中。
如果计算机有多个CPU,每个线程可能在不同的 CPU 上被处理,这意味着每个线程可以拷贝到不同的 CPU cache 中,而volatile变量会跳过CUP cache,每次读都是从内存中读取
volatile的性能经过了很多的优化,建议使用,它读的性能是很高的,几乎和非volatile变量一致,写的性能就差了许多,因为内存界定。
volatile经过写操作后会做两件事,1、同步到系统内存中,2、使其他CUP缓存到的volatile变量的内存地址失效,必须重新从内存中读取

使用场景

这个关键字的使用场景,就是存在原子性问题就需要注意,是可以使用它来保证线程的安全

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