您的位置:首页 > 其它

双重检查锁定及单例模式

2016-08-02 20:35 176 查看
转载自:http://blog.csdn.net/turkeyzhou/article/details/6179951

单例创建

       要理解双重检查锁定是从哪里起源的,就必须理解通用单例创建,如清单 1:
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 对象。构造函数被声明为 private,getInstance()方法只创建一个对象。这个实现适合于单线程程序。然而,当引入多线程时,就必须通过同步来保护getInstance()方法。如果不保护getInstance() 方法,则可能返回 Singleton对象的两个不同的实例。假设两个线程并发调用 getInstance()方法并且按以下顺序执行调用:

       1. 线程1调用getInstance()方法并判断出instance在//1处为 null。

       2. 线程1进入if代码块,但在执行//2处的代码行时被线程2预占。

       3. 线程2调用getInstance()方法并在//1处判断出instance为 null。

       4. 线程2进入if代码块并创建一个新的Singleton对象并在//2处将变量instance分配给这个新对象。

       5. 线程2 在//3处返回Singleton对象引用。

       6. 线程2被线程1预占。

       7. 线程1在它停止的地方启动,并执行//2代码行,这导致创建另一个Singleton对象。

       8. 线程1在//3处返回这个对象。

       结果是getInstance()方法创建了两个Singleton对象,而它本该只创建一个对象。通过同步getInstance()方法从而在同一时间只允许一个线程执行代码,这个问题得以改正,如清单2 所示:
public static synchronized Singleton getInstance() {
if (instance == null) //1
instance = new Singleton(); //2
return instance; //3
}
       清单2中的代码针对多线程访问getInstance()方法运行得很好。然而,其实只有在第一次调用方法时才需要同步。由于只有第一次调用执行了//2处的代码,而只有此行代码需要同步,因此就无需对后续调用使用同步。所有其它调用用于判断instance是否为null,并将其返回。多线程能够安全并发地执行除第一次调用外的所有调用。尽管如此,由于该方法是synchronized的,需要为该方法的每一次调用付出同步的代价,即使只有第一次调用需要同步。

       为使此方法更为有效,一个被称为双重检查锁定的习语就应运而生了。这个想法是为了避免对除第一次调用外的所有调用都实行同步的昂贵代价。同步的代价在不同的 JVM 间是不同的。在早期,代价相当高。随着更高级的JVM的出现,同步的代价降低了,但出入synchronized方法或块仍然有性能损失。

       因为只有清单2中的//2行需要同步,可以只将其包装到一个同步块中,如清单3所示:
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
       清单3中的代码展示了用多线程加以说明的和清单1相同的问题。当instance为null时,两个线程可以并发地进入if语句内部。然后,一个线程进入synchronized块来初始化instance,而另一个线程则被阻断。当第一个线程退出synchronized块时,等待着的线程进入并创建另一个Singleton对象。注意:当第二个线程进入synchronized块时,它并没有检查instance是否为null。

双重检查锁定

       为处理清单3中的问题,需要对instance进行第二次检查。这就是“双重检查锁定”名称的由来。将双重检查锁定习语应用到清单3的结果就是清单4。
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {  //1
if (instance == null)          //2
instance = new Singleton();  //3
}
}
return instance;
}
       双重检查锁定背后的理论是:在//2处的第二次检查使(如清单3中那样)创建两个不同的Singleton对象成为不可能。假设有下列事件序列:

       1. 线程1进入getInstance()方法。

       2. 由于instance为null,线程1在//1处进入synchronized块。

       3. 线程1被线程2预占。
       4. 线程2进入getInstance()方法。
       5. 由于instance仍旧为null,线程2试图获取//1处的锁。然而,由于线程1持有该锁,线程2在//1处阻塞。
       6. 线程2被线程1预占。
       7. 线程1执行,由于在//2处实例仍旧为null,线程1还创建一个 Singleton对象并将其引用赋值给instance。
       8. 线程1退出synchronized块并从getInstance()方法返回实例。
       9. 线程1被线程2预占。
      10.线程2获取//1处的锁并检查instance是否为null。
      11.由于instance是非null的,并没有创建第二个Singleton 对象,由线程1创建的对象被返回。

       双重检查锁定背后的理论是完美的。但仍存在问题,双重检查锁定的问题是:并不能保证它会在单处理器或多处理器计算机上顺利运行。

       上述清单 4 中的//3 行。此行代码创建了一个Singleton对象并初始化变量instance 来引用此对象。这行代码的问题是:在 Singleton构造函数体执行之前,变量instance可能成为非null的。假设为代码行instance = new Singleton(); 执行了下列伪代码: instance =new Singleton();
mem = allocate(); //Allocate memory for Singleton object.

instance = mem; //Note that instance is now non-null, but has not been initialized.
ctorSingleton(instance); //Invoke constructor for Singleton passing instance.
       这段伪代码不仅是可能的,而且是一些JIT编译器上真实发生的。执行的顺序是颠倒的,但鉴于当前的内存模型,这也是允许发生的。JIT 编译器的这一行为使双重检查锁定的问题只不过是一次学术实践而已。

       考虑到当前的双重检查锁定不起作用,加入了另一个版本的代码,如清单 7 所示,从而防止刚才看到的无序写入问题。
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;
}
       看着清单7中的代码,此代码试图避免无序写入问题。它试图通过引入局部变量inst和第二个synchronized 块来解决这一问题。由于当前内存模型的定义,清单7中的代码无效。Java语言规范(Java Language Specification,JLS)要求不能将synchronized块中的代码移出来。但是,并没有说不能将 synchronized 块外面的代码移入 synchronized 块中。

       JIT编译器会在这里看到一个优化的机会。此优化会删除//4和//5处的代码,组合并且生成清单8中所示的代码。
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 = new Singleton();
}
//instance = inst; //5
}
}
}
return instance;
}
       如果进行此项优化,将同样遇到之前讨论过的无序写入问题。

更多参考:http://blog.csdn.net/zero__007/article/details/45644257
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: