单例模式和双检测的加锁(Double-checked locking and the Singleton pattern)

  单例模式在编程中很常见多个线程使用,必须使用某种类型的同步。为了使你的代码更高效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

 

 

你可能感兴趣的:(单例模式和双检测的加锁(Double-checked locking and the Singleton pattern))