实现单例模式的默认方法是:
class MyClass {
private static MyClass instance;
public static MyClass getInstance() {
if (instance == null) {
instance = new MyClass();
}
return instance;
}
}
在一个旧项目中,我试图简化写作:
class MyClass {
private static final MyClass instance = new MyClass();
public static MyClass getInstance() {
return instance;
}
}
但它有时会失败。我只是不知道为什么,我做了默认的方式。 让SSCCE今天在这里发布,我意识到代码可以运行。
所以,我想知道意见.. 这是一个随意的失败代码吗? 第二种方法是否有可能返回null? 我疯了吗?
- 虽然我不知道每个案例的答案是否正确,但@Alfred的回答非常有趣: 我还想指出,单身人士正在测试噩梦,根据大家伙的说法,你应该使用谷歌的依赖注入框架。
答案 0 :(得分:8)
推荐(通过Effective Java 2nd ed)方式是执行“枚举单例模式”:
enum MyClass {
INSTANCE;
// rest of singleton goes here
}
这里的关键见解是枚举值是单实例,就像单例一样。因此,通过制作一个单值枚举,你只需要让自己成为一个单身人士。这种方法的优点在于它完全是线程安全的,并且它还可以安全地防止任何允许人们创建其他实例的漏洞。
答案 1 :(得分:4)
第一个解决方案是(我相信)不是线程安全的。
第二个解决方案(我相信)是线程安全的,但是如果您在MyClass.getInstance()
静态初始化完成之前调用了MyClass
的复杂初始化依赖关系,则可能无效。这可能是你看到的问题。
这两个解决方案都允许某人创建您的(名义上的)单例类的另一个实例。
更强大的解决方案是:
class MyClass {
private static MyClass instance;
private MyClass() { }
public synchronized static MyClass getInstance() {
if (instance == null) {
instance = new MyClass();
}
return instance;
}
}
在现代JVM中,获取锁定的成本微乎其微,前提是锁定没有争用。
编辑 @Nate质疑我关于可能导致问题的静态初始化顺序的陈述。考虑以下(病理)示例:
public ClassA {
public static ClassB myB = ClassB.getInstance();
public static ClassA me = new ClassA();
public static ClassA getInstance() {
return me;
}
}
public ClassB {
public static ClassA myA = ClassA.getInstance();
public static ClassB me = new ClassB();
public static ClassB getInstance() {
return me;
}
}
这两个类有两种可能的初始化顺序。两者都导致在执行方法的类静态初始化之前调用静态方法。这会导致ClassA.myB
或ClassB.myA
初始化为null
。
实际上,静态之间的循环依赖关系不如此明显。但事实仍然是,如果存在循环依赖:1)Java编译器将无法告诉您它,2)JVM不会告诉您它。相反,JVM将默默地选择初始化顺序,而不“理解”您正在尝试执行的操作的语义...可能导致意外/错误。
编辑2 - JLS 12.4.1中对此进行了描述,如下所示:
如§8.3.2.3中的示例所示,初始化代码不受限制的事实允许构造示例,其中类变量的值在其初始化默认值之前可以被观察到,在其初始化表达式之前是评估,但这样的例子在实践中很少见。 (这些示例也可以构造为例如变量初始化;参见§12.5末尾的示例)。这些初始化器提供了该语言的全部功能;程序员必须小心谨慎。 ...
答案 2 :(得分:3)
第二个例子是可取的,因为第一个例子不是线程安全的(如评论中所指出的)。第一个示例使用一种称为lazy instantiation(或延迟初始化)的技术,该技术确保不会创建Singleton实例,除非实际需要它。由于Java处理类加载和静态实例变量初始化的方式,这在Java中并不是必需的。
答案 3 :(得分:2)
我还想指出singletons正在测试噩梦,根据大家伙的说法,你应该使用google的dependency injection framework。
答案 4 :(得分:1)
请记住,您需要声明一个私有构造函数以确保单例属性。
第二种情况可能更简单,只需
class MyClass {
public static final MyClass instance = new MyClass();
private MyClass() {
super()
}
}
`
答案 5 :(得分:1)
正如其他人所说,第一个不是线程安全的。不要打扰它,因为第二个完全正常,并且只有在引用MyClass时才会实例化对象。此外,它使参考最终更好地表达了意图。
请确保声明
private static final MyClass INSTANCE = new MyClass();
是类中的第一个静态声明,用于避免在初始化之前调用getInstance()或INSTANCE的风险。
答案 6 :(得分:1)
不要忘记SingletonHolder模式。请参阅this SO问题。
答案 7 :(得分:0)
我不知道你的问题的答案,但这是我如何构建同样的事情。
class MyClass {
private static MyClass instance;
static {
instance = new MyClass();
}
private MyClass() { }
public static MyClass getInstance() {
return instance;
}
}