1

我一定是在做一些非常愚蠢的事情,但是当我尝试在我的 Singleton 中实例化一个对象时,我得到了一个 ExceptionInInitializerError:

class MySingleton {

  private static MySingleton instance = null;
  private OtherObject obj;

  // Do I instantiate obj here???
  private MySingleton() {
    //obj = new OtherObject();
  }

  // Or here?
  {
    //obj = new OtherObject();
  }

  // Or where? ...

  public static MySingleton getInstance() {
    if (instance == null)
      instance = new MySingleton();
    return instance;
  }

}

我应该在构造函数中创建另一个对象,还是对于单身人士来说总是应该是空的?我在构造函数和初始化程序块中都得到了异常......

这是主要的():

public static void main(String[] args) {
    try {
        MySingleton singleton = MySingleton.getInstance();
    } catch (Error e) {
        System.err.println("Error!");
    }
}
4

6 回答 6

5

您可以通过消除延迟初始化风险(您目前在例外情况下为此付出代价)来避免一些混淆。既然你真的只是返回你的静态实例,为什么不在运行时创建那个静态实例(即,不要等待 null):

class MySingleton {

  private static MySingleton instance = new MySingleton();

  // You could do this here or in the constructor
  // private OtherObject obj = new OtherObject();

  /** Use this if you want to do it in the constructor instead. */
  private OtherObject obj;

  private MySingleton() {
      obj = new OtherObject();
  }

  /** Now you can just return your static reference */
  public static MySingleton getInstance() {
      return instance;
  }

}

如果您注意到,现在一切都是确定性和直接的。您的 MySingleton.instance 在运行时填充并通过静态方法访问MySingleton.getInstance()

我意识到这与原始 GOF 设计模式书的确切模式不匹配,但您会注意到该类的用法实际上是相同的。

编辑:跟进其他答案和评论中提出的一些线程安全点,我将尝试说明问题和 GOF 书中最初提出的解决方案如何是非线程安全的。如需更好的参考,请参阅我在 ACM / Safari 在线书架上拥有并拥有的Java 并发实践。坦率地说,它比我草拟的例子更好,但是,嘿,我们只能努力....

因此,假设我们有两个线程,分别命名为 Thread1 和 Thread2,巧合的是,每个线程同时都调用了 MySingleton.getInstance() 方法。这在现代多核超线程系统中是完全可能的。现在,即使这两个线程碰巧同时到达了这个方法的入口点,也不能保证哪个线程会到达任何特定的语句。所以,你可以看到这样的东西:

// Note that instance == null as we've never called the lazy getInstance() before.

Thread1: if (instance == null)
Thread2: if (instance == null) // both tests pass - DANGER
Thread1:     instance = new MySingleton();
Thread2:     instance = new MySingleton(); // Note - you just called the constructor twice
Thread1: return instance; // Two singletons were actually created 
Thread2: return instance; // Any side-effects in the constructor were called twice

if-null 测试中说明的问题称为竞争条件。你无法知道谁会在什么时候发表什么声明。结果,就像两条线都在相互竞争到悬崖上。

如果您对我的示例进行相同类型的检查,两个线程同时仍在访问 getInstance(),您会看到如下内容:

Thread1: return instance;
Thread2: return instance;

简单化,是的,但这就是我的观点。该对象是很久以前构造的,线程可以指望它的值是一致的。不存在种族。

注意:您仍然需要担心 OtherObject 的构造函数的内容。那里的教训是:并发很难。如果您使构造函数线程安全(您应该这样做),请确保 OtherObject 的作者对您也有同样的礼貌。

于 2010-10-21T21:16:56.943 回答
2

答案很简单,不要使用单例。虽然 GoF 书中有一些好主意,但单例模式并不是其中之一。

正如其他人所说的那样,单例的正确线程安全使用很棘手,请参阅

IBM 关于线程安全单例的文章

实际上,所有单例实现都是一个全局对象。这很糟糕,它污染了命名空间。如果不是不可能的话,它会使正确的单元测试(Junit 等)变得更加困难。你永远不需要它。一个简单的静态工厂类代码更干净,避免了全局污染并且代码更少。

于 2010-10-22T02:41:12.357 回答
1

您应该在构造函数中实例化 OtherObj。给出错误的确切代码是什么?

编辑——以下对我有用

class MySingleton {

  private static MySingleton instance = null;
  private Integer obj;


  private MySingleton() {
    obj = new Integer(2);
  }


  public static MySingleton getInstance() {
    if (instance == null)
      instance = new MySingleton();
    return instance;
  }

}

然后我只是从主循环中调用 getInstance 。你可能想看看

http://en.wikipedia.org/wiki/Singleton_pattern#Java

于 2010-10-21T21:03:28.350 回答
1
class MySingleton {

  private static MySingleton instance = null;
  private OtherObject obj;

  private MySingleton() {
      obj = new OtherObject();
  }

  public static MySingleton getInstance() {
    if (instance == null)
      instance = new MySingleton();
    return instance;
  }

}
于 2010-10-21T21:03:37.367 回答
1

静态初始化部分用于初始化标记为静态的类的成员。由于 OtherObject obj 不是静态的,因此不应在那里初始化。正确的地方是在构造函数中初始化它。

如果obj = new OtherObject()在构造函数中遇到 ExceptionInInitializerError,则问题可能出在另一个类(可能是 OtherObject?)不正确地初始化静态成员。

于 2010-10-21T21:06:10.500 回答
0

Your Singleton class is not at fault here. I would guess a problem in the contructor of the OtherObject class.

A correct syntax would be:

class MySingleton {

  private static MySingleton instance = null;
  private OtherObject obj;

  private MySingleton() {
    obj = new OtherObject();
  }

  public static MySingleton getInstance() {
    if (instance == null)
      instance = new MySingleton();
    return instance;
  }

}

Now, you just need to give us more information about the OtherObject :-)

于 2010-10-21T21:37:21.480 回答