Volatile关键字解析

Volatile 关键字解析

1:禁止指令重排

从单例模型认识Volatile

public class Singleton {
    public static volatile Singleton singleton;
    public static Singleton getSingleton() {
        if (singleton == null) {                     
            synchronized (Singleton.class) {        
                if (singleton == null) {            
                    singleton = new Singleton();      
                }
            }
        }
        return singleton;
    }
}

单例中为什么要使用volatile?
编译器和处理器为了在不改变程序执行结果的前提下,尽可能提高执行效率。会对一些指令进行重新排序。而在一个创建对象的过程中(new Singleton())可以分为4步:

1:加载class对象
2:分配内存空间
3:调用构造函数,初始化实例
4:返回引用地址

当进行重排序的时候可能会出现,当对象没有初始化完成就返回了引用地址,别的线程判定对象不是空,直接拿去用了,但是对象还是个半成品,就可能会导致空指针异常。
所以创建单例时候使用volatile是为了禁止指令重排

2:内存可见性

JMM:

1:每一个线程还存在自己的工作内存,线程的工作内存,保留了被线程使用的变量的工作副本。
2:线程对变量的所有的操作(读,取)都必须在工作内存中完成,而不能直接读写主内存中的变量。
3:不同线程之间也不能直接访问对方工作内存中的变量,线程间变量的值的传递需要通过主内存中转来完成。

本地内存和主内存的关系:

volatile变量在读的时候总能获取到其他线程最后写入的值。

写:当写入一个volatile变量时,将线程工作内存中的变量值刷新到主内存中。
读:当读取一个volatile变量时,首先将改工作内存中的变量设置为无效,重新从主内存中获取最新的有效值。

volatile通过MESI(缓存一致性协议)+ 嗅探 实现的。嗅探可以知道其他内存中的volatile修饰的变量在其他线程中是否已经过期,过期了就会直接从主内存中读取数据。

3:Volatile使用场景

1:Volatile只能保证可见性和有序性,不能保证原子性。在多线程情况下,Volatile变量不应该做非原子操作比如:“i++”,可以做:“i=true”
2:Volatile用的时候要谨慎,Volatile为了可见性,使用了嗅探,如果变量太多,会导致“总线风暴”;

4:总结

1:volatile修饰符适用于以下场景:某个属性被多个线程共享,其中有一个线程修改了此属性,其他线程可以立即得到修改后的值,比如booleanflag;或者作为触发器,实现轻量级同步。
2:volatile属性的读写操作都是无锁的,它不能替代synchronized,因为它没有提供原子性和互斥性。因为无锁,不需要花费时间在获取锁和释放锁_上,所以说它是低成本的。
3:volatile只能作用于属性,我们用volatile修饰属性,这样compilers就不会对这个属性做指令重排序。
4:volatile提供了可见性,任何一个线程对其的修改将立马对其他线程可见,volatile属性不会被线程缓存,始终从主 存中读取。
5:volatile提供了happens-before保证,对volatile变量v的写入happens-before所有其他线程后续对v的读操作。
6:volatile可以在单例双重检查中实现可见性和禁止指令重排序,从而保证安全性。

你可能感兴趣的:(java)