单例模式在编程中很常见。当多个线程使用时,必须使用某种类型的同步。为了使你的代码更高效,Java程序员在单例模式中使用双重检测锁定来限制并发执行的代码量。但是,由于Java内存模型的一些鲜为人知的细节,这种双重检查锁定是不能保证工作,它会偶尔失败。此外,其失败的原因并不明显,涉及到Java内存模型的内部细节。并且是很难追查的。在本文的其余部分,我们将仔细研究双重检查锁定了解它什么时候就发生了。
单例模式的习惯写法
Listing1
import java.util.*;
class Singleton
{
private static Singleton instance;
private Vector v;
private boolean inUse;
private Singleton()
{
v = new Vector();
v.addElement(new Object());
inUse = true;
}
public static Singleton getInstance()
{
if (instance == null) //1
instance = new Singleton(); //2
return instance; //3
}
}
这个类保证了Singleton实例只能被创建一次,是靠将构造方法的访问权限设为私有的,getInstance()方法只能创建一个Singleton对象。这种设计在单线程中是很好的。但是在多线程环境下,你必须通过加锁机制来保护getInstance()方法,否则会创建出两个不同的实例对象。假设现在有两个不同的线程来访问getInstance()方法,可能会按照下面的顺序来执行:
1.线程1调用getInstance()方法,在1处判断结果为true
2.线程1进入if语句块中,但是在执行2处的代码时,开始阻塞了,被线程2赶超
3.线程2调用getInstance()方法,在1处判断结果也为true
4.线程2进入if语句块中,继续执行2处的代码,创建了一个实例,并让instance变量指向了该对象。
5.线程2返回了Singleton对象在第3行中的代码
6.线程1开始继续执行,从被打断的地方
7.线程1在2处又创建了一个实例对象,并返回
上面导致创建了两个实例对象,而该设计模式的目的是只创建一个实例对象。这个问题可以通过用synchronized修饰方法来解决,如下所示:Listing2
public static synchronized Singleton getInstance()
{
if (instance == null) //1
instance = new Singleton(); //2
return instance; //3
}
代码经过这么改进确实解决了上面的问题。通过代码的仔细分析,会发现,每次调用getInstance()方法,都会进行加锁,这显然是不合理的,除了第一次在创建实例的时候需要锁的保护,后来的调用是不再需要的。锁机制是会影响效率的。
为了提高代码执行的效率,双重检查锁定方法被引入了。它的目的就是为了解决一个问题就是除了第一次需要锁保护外,其它是不需要的。
代码改进如下所示:Listing3
public static Singleton getInstance()
{
if (instance == null)
{
synchronized(Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
Listing3中的代码存在Listing1的同样的问题,多个线程可以同时进入if语句块。例如:当线程1正要创建实例的时候被阻塞了,这时线程2仍然能进入if语句块中,这肯定就会再次创建一个实例出来。因此,我们需要再次去check是否为空。代码如下:Listing4
public static Singleton getInstance()
{
if (instance == null)
{
synchronized(Singleton.class) { //1
if (instance == null) //2
instance = new Singleton(); //3
}
}
return instance;
}
在理论上双重检查保证了不会再次创建实例,但是在实际中却不是这样的。原因是在JVM中,被称为“out-of-order writes”是导致问题的主要原因。
当在执行3处的代码时,可能发生这样的一种情况,还没有初始化完毕,就已经赋给了instance,这时另一个线程判断instance时就不为空了,就会返回一个部分初始化的实例对象。代码做了如下改进:Listing5
public static Singleton getInstance()
{
if (instance == null)
{
synchronized(Singleton.class) { //1
Singleton inst = instance; //2
if (inst == null)
{
synchronized(Singleton.class) { //3
inst = new Singleton(); //4
}
instance = inst; //5
}
}
}
return instance;
}
改进的地方就是将构造方法的调用和赋值分开来写,只在调用构造方法中加锁,就是new的时候。后续参见
http://www.ibm.com/developerworks/java/library/j-dcl/index.html