public class DoubleCheck {
private volatile static DoubleCheck instance;
private DoubleCheck()
{
}
public static DoubleCheck getSingleton()
{
if (instance==null)
{
synchronized(DoubleCheck.class)
{
if(instance==null)
{
instance = new DoubleCheck();
}
}
}
return instance;
}
}
首先判断instance是否为null,如果确实为null,则进入一个synchronize包围的代码块,相当于上了锁,进入了临界区,为了防止在判断为null到进入临界区的过程中,有线程对其new了一个实例出来,再上锁完成之后,在对instance是否为null进行一次判断。
上面是一个单例模式双重检查锁的实现。
上述的重点是:
1. 使用了volatile关键字。
2. 两次判断 instance==null
3. 对类加锁
先看第一个,使用volatile关键字。
如果没有使用volatile这一关键字会出现什么情况呢。
先来说说新建对象之一操作,它不是一个原子操作。这个操作可以分为4部分:
1、栈内存开辟空间给对象引用
2、堆内存开辟空间准备初始化对象
3、初始化对象
4、栈中引用指向这个堆内存空间地址
JVM会进行指令重排,那么指令重排之后可能会是1、2、4、3;这在多线程环境下就会出问题,这里到4的时候对象instance已经指向了一块堆内存!=null ,只是这块堆内存还没初始化完成,使用的时候抛NullPointException。
volatile可以保证可见性,以及一定程度上防止重排序。
保证可见性:使用该变量必须重新去主内存读取,修改了该变量必须立刻刷新主内存。
防止重排序:通过插入内存屏障。