volatile
1.保证可见性
可见性首先得了解Java的内存模型JMM
Java的共享变量都存在主内存中包括实例对象,数组对象,静态对象.而线程独有的存在自己的本地内存中.线程的工作内存保存了该线程用到的变量和主内存的副本拷贝,线程对变量的操作都在工作内存中进行。线程不能直接读写主内存中的变量。这个时候如果单线程操作,不会出现数据不一致现象.
而当多线程操作时,线程B改变了主内存的共享变量.线程A用的还是以前共享变量的副本.这个时候如果A想用B改变后的共享变量,就需要加入volatile关键字.对象加入volatile关键字后在更改后会通知所有线程这个值已经被修改.这个时候当其他线程需要用到这个变量的时候,会去主内存拷贝新变量.
2.不保证原子性
原子性是指在一次操作或者多次操作中,要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行。
要解决原子性问题可以加锁,或者使用Atomic原子类.
3.禁止指令重排
指令重排是编译器在单线程情况下不影响最终结果时根据代码性能进行优化,会对前后没有依赖关系的代码进行重排序,优化代码执行顺序.在单线程中指令重排没有影响,但是在多线程情况下进行指令重排就会造成很多问题.单例模式中使用volatile就是需要禁止指令重排
懒汉式单例双重检测:
// volatile 保证可见性和禁止指令重排序
private static volatile Singleton singleton;
public static Singleton getInstance() {
// 第一次检查
if (singleton == null) {
// 同步代码块
synchronized(this.getClass()) {
// 第二次检查
if (singleton == null) {
// 对象的实例化是一个非原子性操作
singleton = new Singleton();
}
}
}
return singleton;
}
}
对象实例化分为三步操作:(1)分配内存空间,(2)初始化实例,(3)返回内存地址给引用。操作23没有依赖关系,在对象实例化的过程中可能出现重排序.假设线程 A 在执行创建对象时,(2)和(3)进行了重排序,如果线程 B 在线程 A 执行(3)时拿到了引用地址,并在第一个检查中判断 singleton != null 了,但此时线程 B 拿到的不是一个完整的对象,在使用对象进行操作时就会出现问题。