2010-09-01 56 views
9

为什么认为模式被破坏?我看起来很好吗?有任何想法吗?双重锁定锁定模式:是否损坏?

public static Singleton getInst() { 
    if (instace == null) createInst(); 
    return instace; 
} 

private static synchronized createInst() { 
    if (instace == null) { 
     instace = new Singleton(); 
    } 
} 
+2

通过使用DI/IOC容器并允许容器控制对象的生命周期,而不是将此类逻辑嵌入对象本身,您可以完全避免此问题....不是一个答案,而是需要思考的问题。 – Stimul8d 2010-09-01 12:28:09

+0

问题在这里发布的代码是否算作双重检查锁定的示例?锁正在被检查一次。 – 2010-09-01 13:56:11

+0

请参阅http://stackoverflow.com/questions/3578604/how-to-solve-the-double-checked-locking-is-broken-declaration-in-java/3578674#3578674 – irreputable 2010-09-01 18:14:43

回答

21

乍一看看起来不错,但这种技术有许多微妙的问题,通常应该避免。例如,考虑事件的顺序如下:

  1. 线程A通知该值 没有初始化,所以它获得 锁,并开始初始化 值。
  2. 由编译器生成的代码被允许 A已完成 执行初始化之前的共享变量来更新到 指向部分构造 对象。
  3. 线程B注意到共享的 变量已被初始化(或出现 ),并返回其值。 因为线程B认为值 已经被初始化,所以它不会获取锁定 。如果B使用 对象,则在由A完成的 初始化被 B看到之前,程序可能会崩溃。

你可以通过使用“挥发性”关键字来正确处理您的单实例

+2

+1 - Java中双重检查锁定模式问题的唯一答案! – helios 2010-09-01 10:08:39

+1

挥发物仍然带来一些问题,看看我的答案。 – atamanroman 2010-09-01 10:31:04

+1

挥发性或惰性加载都不是正确的方法,而是使用静态初始化程序 – 2010-09-01 13:55:04

7

我不知道它是否坏了,但它不是真正的最有效的解决方案,因为同步这很昂贵。 更好的方法是使用'Initialization On Demand Holder Idiom',它会在你的第一次请求时将你的单例加载到内存中,就像名字所暗示的那样,这就是懒加载。 这个习语最大的好处是你不需要同步,因为JLS确保类加载是连续的。关于这个问题

详细的维基百科条目:http://en.wikipedia.org/wiki/Initialization_on_demand_holder_idiom

另一件事要记住的是,由于依赖注入框架如Spring和吉斯已经出现,正在创建创建和这些容器管理的类实例,他们将如果需要,可以为你提供一个Singleton,所以除非你想学习模式背后的想法,否则不值得打破它,这很有用。 另请注意,这些IOC容器提供的单例是每个容器实例的单例,但通常每个应用程序都有一个IOC容器,所以它不会成为问题。

+1

只创建实例同步 – 2010-09-01 10:06:51

+1

好的习惯用法。 (无论如何,问题仍然没有答案)。我应该补充说,与较新的JVM相比,同步的成本更低。 – helios 2010-09-01 10:13:07

4

Initialization On Demand Holder Idiom避免这种情况,是啊,就是这样:

public final class SingletonBean{ 

    public static SingletonBean getInstance(){ 
     return InstanceHolder.INSTANCE; 
    } 

    private SingletonBean(){} 

    private static final class InstanceHolder{ 
     public static final SingletonBean INSTANCE = new SingletonBean(); 
    } 

} 

虽然约书亚布洛赫还建议在枚举单件模式Effective Java第2章第3项:

// Enum singleton - the prefered approach 
public enum Elvis{ 
    INSTANCE; 
    public void leaveTheBuilding(){ ... } 
} 
6

问题是fo下降:您的JVM可能会重新排列您的代码,并且字段对于不同的线程并不总是相同的。看看这个:http://www.ibm.com/developerworks/java/library/j-dcl.html。使用volatile关键字应该可以解决这个问题,但是它在java 1.5之前破坏了。

大部分时间单检锁是不是足够快的多,试试这个:

// single checked locking: working implementation, but slower because it syncs all the time 
public static synchronized Singleton getInst() { 
    if (instance == null) 
     instance = new Singleton(); 
    return instance; 
} 

也看看有效的Java,在那里你会发现关于这个话题的伟大篇章。

总结一下:不要做双重检查锁定,有更好的idoms。

11

整个讨论是一个巨大的,无尽的大脑浪费时间。 99.9%的时间,单身人士没有任何显着的设置成本,并且没有任何理由设计实现非同步保证延迟加载。

这是你如何写一个Singleton在Java中:

public class Singleton{ 
    private Singleton instance = new Singleton(); 
    private Singleton(){ ... } 
    public Singleton getInstance(){ return instance; } 
} 

更重要的是,让一个枚举:

public enum Singleton{ 
    INSTANCE; 
    private Singleton(){ ... } 
} 
+0

您可能还想使该类最终(尽管私有构造函数朝着该方向) – 2010-09-01 10:54:45

+1

+1,双重检查锁定是所有过早纳米优化的母体 – flybywire 2010-09-02 12:17:15

+0

对于枚举构造函数,private是多余的。编译器将使其保密。它在规范中写下来:“在枚举声明中,没有访问修饰符的构造函数声明是私有的。”请参见https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.9.2 – user674669 2017-04-16 07:25:53

2

这不回答你的问题(别人已经做过),但我想告诉你我使用单例/懒惰初始化对象的经验:

我们在代码中有几个单例。一旦我们必须向一个单例添加一个构造函数参数并且存在严重问题,因为此单例的构造函数是在getter上调用的。当时只以下可能的解决方案:

  • 提供一种用于需要初始化该单的对象的静态吸气剂(或另一单体)
  • 传递对象初始化单作为参数用于吸气或
  • 通过传递实例来摆脱单身人士。

最后,最后的选择是要走的路。现在我们在应用程序启动时初始化所有对象并传递所需的实例(可能是一个小接口)。我们没有后悔这个决定,因为

  • 一段代码的依赖是非常明确的,
  • 我们可以测试我们的代码更通过提供所需的对象的虚拟实现方式更容易。
+3

Singleton模式确实被过度使用,并且通常是设计不佳的标志 – 2010-09-01 13:49:14

2

这里的大多数答案都是正确的,说明它为什么会被破坏,但是不正确或者提出可疑的解决方案。

如果你真的,真的必须使用单(在大多数情况下,你不应该,因为它破坏的可测性,结合了有关如何构建一个类与类的行为逻辑,乱丢使用的类单身人士知道如何获得一个,并导致更脆弱的代码),并担心同步,正确的解决方案是使用a static initializer实例化实例。

private static Singleton instance = createInst(); 

public static Singleton getInst() { 
    return instance ; 
} 

private static synchronized createInst() { 
    return new Singleton(); 
} 

Java语言规范保证了静态initiailzers将只运行一次,当一个类加载的第一次,并在保证线程安全的方式。