双重校验锁实现对象单例

  • 在Java中,volatile关键字除了可以保证变量的可见性,还有一个重要的作用就是防止JVM的指令重排序
  • 如果将变量声明为volatile,那么在对这个变量进行读写操作时,会通过插入特定的内存屏障的方式来禁止指令重排
  • unsafe类中的内存屏障相关方法:
    • public native void loadFence();:加载屏障,确保在它之前的读操作不会被重排序到它之后
    • public native void storeFence();:存储屏障,确保在它之前的写操作不会被重排序到它之后
    • public native void fullFence();:完全屏障,确保在它之前的读写操作都不会被重排序到它之后

1. 双重校验锁实现单例模式(线程安全)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Singleton {

private volatile static Singleton uniqueInstance;

private Singleton() {
}

public static Singleton getUniqueInstance() {
//先判断对象是否已经实例过,没有实例化过才进入加锁代码
if (uniqueInstance == null) {
//类对象加锁
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
  • uniqueInstance采用volatile关键字修饰也是很有必要的uniqueInstance = new Singleton(); 这行代码实际上可以分解为以下三个步骤:
    1. uniqueInstance分配内存空间
    2. 初始化uniqueInstance
    3. uniqueInstance指向分配的内存地址
  • 由于JVM的指令重排序优化,执行顺序可能变成1->3->2,这样单线程下不会有问题,但是多线程下就会出现某个线程获得没有初始化的实例。