2

请详细解释一下双重检查单例..!它有什么优点和缺点..!! 我在下面有这个..我怎么能把它作为一个双重检查单例..

 private DataBaseDAO() { }
        public static synchronized DataBaseDAO getInstance() {
            if (dao == null) {
                dao = new DataBaseDAO();
                }
            return dao;
            }
        }
    }
4

3 回答 3

3

考虑下面的代码

public static Singleton getInstance()
{
  if (instance == null)
  {
    synchronized(Singleton.class) {  //1
      if (instance == null)          //2
        instance = new Singleton();  //3
    }
  }
  return instance;
}

双重检查锁定背后的理论是 //2 处的第二次检查使得不可能像清单中那样创建两个不同的 Singleton 对象

考虑以下事件序列:

线程 1 进入 getInstance() 方法。

线程 1 在 //1 处进入同步块,因为实例为空。

线程 1 被线程 2 抢占。

线程 2 进入 getInstance() 方法。

线程 2 尝试在 //1 处获取锁,因为实例仍然为空。但是,因为线程 1 持有锁,所以线程 2 在 //1 处阻塞。

线程 2 被线程 1 抢占。

线程 1 执行并且因为实例在 //2 处仍然为空,所以创建了一个单例对象并将其引用分配给实例。

线程 1 退出同步块并从 getInstance() 方法返回实例。

线程 1 被线程 2 抢占。

线程 2 在 //1 处获取锁并检查实例是否为空。

因为 instance 不为空,所以不会创建第二个 Singleton 对象,而是返回由线程 1 创建的对象。

双重检查锁定背后的理论是完美的。不幸的是,现实完全不同。双重检查锁定的问题在于不能保证它可以在单处理器或多处理器机器上工作。双重检查锁定失败的问题不是由于 JVM 中的实现错误,而是由于当前的 Java 平台内存模型。内存模型允许所谓的“乱序写入”,这也是这个习惯用法失败的主要原因。

于 2012-08-08T16:37:15.157 回答
3

"Effective Java Second Edition"中所述,一个好的解决方案是使用 "Singleton-as-enum" 模式:

public enum DataBaseDAO() {
    INSTANCE;
}

并通过DataBaseDAO.INSTANCE. 这保证在所有情况下,都会有一个且只有一个DataBaseDAO.

于 2012-08-08T16:42:10.017 回答
0

在您的代码实现中,您正在执行静态方法级同步。如果 DatabaseDAO 类中还有其他静态方法,那么即使这些方法可以使用这些方法独立工作,它们也不能。您可以通过执行块级同步来避免这种情况。在多线程应用程序中,这是实现单例对象的最安全方法之一 -

private DataBaseDAO {
    private static final Object lock = new Object();
    private static DataBaseDAO dao = null;
    private DataBaseDAO() { }
    public static DataBaseDAO getInstance() {
        if (dao == null) {
            synchronize(lock) {
               if (dao == null) {
                   dao = new DataBaseDAO();
                }
            }
        }
        return dao;

    }
}

作为对象锁,您也可以使用DataBaseDAO.class.

解释:多个线程同时访问该getInstance()方法。在交错场景中,2 个或更多线程可以通过第一次if检查,但只有一个线程会获取lock对象上的锁。获得锁的线程将能够创建实例。如果第一个线程已经获得锁(并且第一个线程尚未释放它) ,其他线程即使通过了第一次if检查也将无法获得锁。

现在,假设第一个线程释放了锁,这意味着它也实例化了 dao 对象。当其他线程被调度时,每个线程都会获取锁,但是第二次if检查失败,会立即释放锁,获取已经实例化的dao对象。

一旦创建了对象,之后所有尝试访问该getInstance()方法的线程都不会进行任何同步,因为第一个线程if本身将失败。

于 2012-08-08T16:39:45.710 回答