volatile

volatile 的主要作用有两个方面【可见性/顺序性[防止指令重排序]】

可见性:

首先熟悉一下JVM的内存工作模型[注意这里的工作模型不是堆/栈/方法区这些],

线程本身并不直接与主内存进行数据的交互,而是通过线程的工作内存来完成相应的操作。这也是导致线程间数据不可见的本质原因。因此要实现volatile变量的可见性,直接从这方面入手即可。对volatile变量的写操作与普通变量的主要区别有两点:1 修改volatile变量时会强制将修改后的值刷新的主内存中。 2 修改volatile变量后会导致其他线程工作内存中对应的变量值失效。因此,再读取该变量值的时候就需要重新从读取主内存中的值。通过这两点就可以很好的解决可见性问题。
 
顺序性[防止指令重排序]:
volatile之所以能够阻止指令重排,是因为底层JVM里面利用了内存屏障来实现的,内存屏障主要有三点功能:
  1. 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
  2. 它会强制将对缓存的修改操作立即写入主存;
  3. 如果是写操作,它会导致其他CPU中对应的缓存行无效。

举个栗子:

public class Singleton {
    public static volatile Singleton instance = null;
    private Singleton() {
    }
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (instance) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
双重校验的写法:第一次判断是否为null是为了拒绝掉当对象不为空的时候剩余的线程。里面加锁是为了当对象为null的时候,此时同时进来两个线程(A和B两个线程),我们要保证只有一个线程才可以初始化对象,所以在这里面加上了锁,这样A拿到了锁进去初始化对象,然后进行返回,B再进去此时发现不为null,那么就不执行初始化的过程。这样就能保证上面的单例模式的正常运行,同时为系统也是节约了许多开销(避免每个线程进来加锁--懒汉式写法)
 
并发编程中谈及到的无非是可见性、有序性及原子性,为什么volatile不能保障原子性?
因为 volatile 只能修饰变量,但是有些变量的操作就没办法保障了,比如 i++  ,看JVM变异的指令其实是多条语句的,所以不能保障原子性,只能通过加锁的形式帮助去解决。
 
 
 
原文地址:https://www.cnblogs.com/junbaba/p/14131412.html