在Java中实现单例设计模式的有效方法是什么?
当前回答
Stu Thompson发布的解决方案在Java 5.0及更高版本中有效。但我宁愿不使用它,因为我认为它容易出错。
人们很容易忘记不稳定的说法,也很难理解为什么有必要这样做。如果没有易失性,由于双重检查锁定反模式,该代码将不再是线程安全的。有关这一点的更多信息,请参阅《Java并发实践》第16.2.4段。简而言之:此模式(在Java5.0之前或没有volatile语句)可能返回对Bar对象的引用,该对象(仍然)处于错误状态。
这种模式是为性能优化而发明的。但这真的不再是一个真正的问题了。下面的惰性初始化代码很快,更重要的是,更容易阅读。
class Bar {
private static class BarHolder {
public static Bar bar = new Bar();
}
public static Bar getBar() {
return BarHolder.bar;
}
}
其他回答
忘记延迟初始化;这太有问题了。这是最简单的解决方案:
public class A {
private static final A INSTANCE = new A();
private A() {}
public static A getInstance() {
return INSTANCE;
}
}
另一个经常用来反对单态的论点是它们的可测试性问题。出于测试目的,单体不容易被模拟。如果这是一个问题,我想做以下轻微修改:
public class SingletonImpl {
private static SingletonImpl instance;
public static SingletonImpl getInstance() {
if (instance == null) {
instance = new SingletonImpl();
}
return instance;
}
public static void setInstance(SingletonImpl impl) {
instance = impl;
}
public void a() {
System.out.println("Default Method");
}
}
添加的setInstance方法允许在测试期间设置单例类的实体模型实现:
public class SingletonMock extends SingletonImpl {
@Override
public void a() {
System.out.println("Mock Method");
}
}
这也适用于早期初始化方法:
public class SingletonImpl {
private static final SingletonImpl instance = new SingletonImpl();
private static SingletonImpl alt;
public static void setInstance(SingletonImpl inst) {
alt = inst;
}
public static SingletonImpl getInstance() {
if (alt != null) {
return alt;
}
return instance;
}
public void a() {
System.out.println("Default Method");
}
}
public class SingletonMock extends SingletonImpl {
@Override
public void a() {
System.out.println("Mock Method");
}
}
这有一个缺点,就是将此功能也暴露给普通应用程序。其他编写该代码的开发人员可能会尝试使用“setInstance”方法来更改特定函数,从而改变整个应用程序的行为,因此该方法的javadoc中至少应该包含一个良好的警告。
尽管如此,对于模型测试的可能性(当需要时),这种代码暴露可能是可以接受的代价。
使用枚举:
public enum Foo {
INSTANCE;
}
乔舒亚·布洛赫(Joshua Bloch)在Google I/O 2008的“有效Java重载”演讲中解释了这种方法:视频链接。另请参见其演示文稿的幻灯片30-32(effecte_java_reloaded.pdf):
实现可串行化Singleton的正确方法公共枚举Elvis{实例;private final String[]收藏夹歌曲={“猎犬”,“心碎酒店”};public void printFavorites(){System.out.println(Arrays.toString(收藏夹歌曲));}}
编辑:“有效Java”的在线部分说:
“这种方法在功能上等同于公共字段方法,只是它更加简洁,免费提供了序列化机制,并且即使在复杂的序列化或反射攻击的情况下也能提供针对多个实例化的铁腕保证。虽然这种方法尚未被广泛采用,但单个元素枚举类型是实现一个公共字段的最佳方式。”英格顿。"
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {
if (INSTANCE != null)
throw new IllegalStateException(“Already instantiated...”);
}
public synchronized static Singleton getInstance() {
return INSTANCE;
}
}
由于我们在getInstance之前添加了Synchronized关键字,因此在两个线程同时调用getInstance的情况下,我们避免了竞争条件。
免责声明:我刚刚总结了所有很棒的答案,并用自己的话写了出来。
在实施Singleton时,我们有两种选择:
延迟加载早期加载
延迟加载增加了位开销(老实说,很多),所以只有当您有非常大的对象或大量的构造代码,并且有其他可访问的静态方法或字段(可能在需要实例之前使用)时,才需要使用延迟初始化。否则,选择提前加载是一个不错的选择。
实现单例的最简单方法是:
public class Foo {
// It will be our sole hero
private static final Foo INSTANCE = new Foo();
private Foo() {
if (INSTANCE != null) {
// SHOUT
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
return INSTANCE;
}
}
一切都很好,除了它是一个早期加载的单例。让我们尝试延迟加载的单例
class Foo {
// Our now_null_but_going_to_be sole hero
private static Foo INSTANCE = null;
private Foo() {
if (INSTANCE != null) {
// SHOUT
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
// Creating only when required.
if (INSTANCE == null) {
INSTANCE = new Foo();
}
return INSTANCE;
}
}
到目前为止还不错,但我们的英雄在与多个邪恶线程单独战斗时将无法生存,这些线程需要我们英雄的许多实例。因此,让我们保护它免受邪恶的多线程攻击:
class Foo {
private static Foo INSTANCE = null;
// TODO Add private shouting constructor
public static Foo getInstance() {
// No more tension of threads
synchronized (Foo.class) {
if (INSTANCE == null) {
INSTANCE = new Foo();
}
}
return INSTANCE;
}
}
但这还不足以保护英雄,真的!!!这是我们能/应该做的最好的事情来帮助我们的英雄:
class Foo {
// Pay attention to volatile
private static volatile Foo INSTANCE = null;
// TODO Add private shouting constructor
public static Foo getInstance() {
if (INSTANCE == null) { // Check 1
synchronized (Foo.class) {
if (INSTANCE == null) { // Check 2
INSTANCE = new Foo();
}
}
}
return INSTANCE;
}
}
这被称为“双重检查锁定习惯用法”。人们很容易忘记不稳定的说法,也很难理解为什么有必要这样做。详细信息:“双重检查锁定失败”声明
现在我们确定了邪恶的线索,但残酷的连载呢?我们必须确保即使在反序列化时也不会创建新对象:
class Foo implements Serializable {
private static final long serialVersionUID = 1L;
private static volatile Foo INSTANCE = null;
// The rest of the things are same as above
// No more fear of serialization
@SuppressWarnings("unused")
private Object readResolve() {
return INSTANCE;
}
}
方法readResolve()将确保返回唯一的实例,即使对象在我们的程序的上一次运行中被序列化。
最后,我们添加了足够的线程和序列化保护,但我们的代码看起来又大又难看。让我们给我们的英雄一个新形象:
public final class Foo implements Serializable {
private static final long serialVersionUID = 1L;
// Wrapped in a inner static class so that loaded only when required
private static class FooLoader {
// And no more fear of threads
private static final Foo INSTANCE = new Foo();
}
// TODO add private shouting construcor
public static Foo getInstance() {
return FooLoader.INSTANCE;
}
// Damn you serialization
@SuppressWarnings("unused")
private Foo readResolve() {
return FooLoader.INSTANCE;
}
}
是的,这是我们的英雄:)
由于行private static final Foo INSTANCE=new Foo();仅在实际使用FooLoader类时执行,这会处理延迟实例化,并保证线程安全。
我们已经走到了今天。以下是实现我们所做一切的最佳方式:
public enum Foo {
INSTANCE;
}
内部将被视为
public class Foo {
// It will be our sole hero
private static final Foo INSTANCE = new Foo();
}
就是这样!不再担心序列化、线程和丑陋的代码。ENUMS单例也被延迟初始化。
该方法在功能上等同于公共领域方法,除了它更简洁,提供了序列化机制免费提供,并提供多重保险实例化,即使面对复杂的序列化或反射攻击。虽然这种方法尚未被广泛采用,单个元素枚举类型是实现单例的最佳方式。
-《有效的Java》中的约书亚·布洛赫
现在您可能已经意识到为什么ENUMS被认为是实现单例的最佳方式,并感谢您的耐心:)
在我的博客上更新了它。
推荐文章
- 在流中使用Java 8 foreach循环移动到下一项
- 访问限制:'Application'类型不是API(必需库rt.jar的限制)
- 用Java计算两个日期之间的天数
- 如何配置slf4j-simple
- 在Jar文件中运行类
- 带参数的可运行?
- 我如何得到一个字符串的前n个字符而不检查大小或出界?
- 我可以在Java中设置enum起始值吗?
- Java中的回调函数
- c#和Java中的泛型有什么不同?和模板在c++ ?
- 在Java中,流相对于循环的优势是什么?
- Jersey在未找到InjectionManagerFactory时停止工作
- 在Java流是peek真的只是调试?
- Recyclerview不调用onCreateViewHolder
- 将JSON字符串转换为HashMap