我必须做一些非常愚蠢的事情,但是当我尝试在我的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;
}
}
我应该在构造函数中创建另一个对象,还是对于单例来说总是应该为空?我在构造函数和初始化程序块中都得到了异常......
这是main():
public static void main(String[] args) {
try {
MySingleton singleton = MySingleton.getInstance();
} catch (Error e) {
System.err.println("Error!");
}
}
答案 0 :(得分:5)
您可以通过消除lazy initialization风险(您目前在例外中支付的风险)来避免一些混淆。由于您实际上只是返回静态实例,为什么不在运行时创建该静态实例(即,不要等待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 Concurrency in Practice。坦率地说,这比我描绘的例子更好,但是,嘿,我们可以努力....
因此,假设我们有两个名为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测试中说明的问题称为race condition。你什么时候不知道是什么声明。结果,就像两条线都相互竞争到了悬崖。
如果你看看我的例子同样的检查,两个线程仍在同一时刻点击getInstance(),你会看到类似这样的事情:
Thread1: return instance;
Thread2: return instance;
简单,是的,但这是我的观点。该对象很久以前构造了一次,并且线程可以指望它的值是一致的。没有种族。
注意:您仍然需要担心OtherObject的构造函数的内容。那里的教训是:并发性很难。如果您正在使构造函数线程安全(您应该这样做),请确保OtherObject的作者为您提供相同的礼貌。
答案 1 :(得分:2)
答案很简单,不要使用单身人士。虽然GoF书有一些好主意,但单身模式不是其中之一。
正如其他人所说的那样,正确安全地使用单例是非常棘手的,参见
IBM article on thread safe singletons
实际上,所有Singleton实现都是一个全局对象。这很糟糕,它污染了命名空间。如果不是不可能的话,它使得适当的单元测试(Junit等)变得更加困难。你永远不需要它。一个简单的静态工厂类是更清晰的代码,避免了全局污染并且代码更少。
答案 2 :(得分: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。你可能想看看
答案 3 :(得分: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;
}
}
答案 4 :(得分:1)
静态初始化部分用于初始化标记为static的类的成员。由于OtherObject obj不是静态的,因此不应该在那里进行初始化。正确的位置是在构造函数中初始化它。
如果在构造函数中有obj = new OtherObject()
时遇到ExceptionInInitializerError,问题可能出在另一个类(也许是OtherObject?)上,错误地初始化静态成员。
答案 5 :(得分:0)
你的Singleton类在这里没有错。我猜想在OtherObject类的构造函数中有一个问题。
正确的语法是:
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;
}
}
现在,您只需要向我们提供有关OtherObject的更多信息: - )