public class Singleton {

	//没有volatile线程可能访问到的是一个没有初始化的对象
    private volatile static Singleton instance;
    
    private Singleton() {
    }
    
    public static Singleton getInstance() { 
        if (instance == null) {
            synchronized (Singleton.class) {      //静态方法的同步锁为类的class对象,即Singleton.class
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        
        return instance;
    }
}

注:通过第8行判断的每个线程会依次获得锁进入临界区,所以进入临界区后还要再判断一次单例类是否已被其它线程实例化,以避免多次实例化。由于双重加锁实现仅在实例化单例类时需要加锁,所以会带来性能上的提升。另外需要注意的是双重加锁要对 instance 域加上 volatile 修饰符。由于 synchronized 并不是对 instance 实例进行加锁(因为现在还并没有实例),所以线程在执行完第11行修改 instance 的值后,应该将修改后的 instance 立即写入主存(main memory),而不是暂时存在寄存器或者高速缓冲区(caches)中,以保证新的值对其它线程可见,避免其他线程进行初始化。

volatile关键字的作用:

实例化对象的那行代码( instance = new Singleton();),实际上可以分解成以下三个步骤:
1.分配内存空间
2.初始化对象
3.将对象指向刚分配的内存空间

但是有些编译器为了性能的原因,可能会将第二步和第三步进行重排序,顺序就成了:
1.分配内存空间
2.将对象指向刚分配的内存空间
3.初始化对象

现在考虑重排序后,两个线程发生了以下调用:
Time Thread A Thread B
T1 检查到uniqueSingleton为空
T2 获取锁
T3 再次检查到uniqueSingleton为空
T4 为uniqueSingleton分配内存空间
T5 将uniqueSingleton指向内存空间
T6 检查到uniqueSingleton不为空
T7 访问uniqueSingleton(此时对象还未完成初始化)
T8 初始化uniqueSingleton

在这种情况下,T7时刻线程B对uniqueSingleton的访问,访问的是一个初始化未完成的对象。

解决: 使用volatile关键字修饰instance对象,使用了volatile关键字后,重排序被禁止,上述编译器为了性能优化进行的重排序行为就不允许发生了。