【问题标题】:proper usage of synchronized singleton?正确使用同步单例?
【发布时间】:2014-03-27 23:58:53
【问题描述】:

所以我正在考虑建立一个业余爱好项目,这是一种特殊的东西,只是为了复习我的编程/设计。

它基本上是一个多线程的网络蜘蛛,更新相同的数据结构对象->int。

因此,为此使用数据库绝对是矫枉过正,而我唯一能想到的就是用于包含我的数据结构的线程安全单例。 http://web.archive.org/web/20121106190537/http://www.ibm.com/developerworks/java/library/j-dcl/index.html

我应该考虑其他方法吗?

【问题讨论】:

    标签: java multithreading singleton synchronized


    【解决方案1】:

    双重检查锁定已被证明是不正确且有缺陷的(至少在 Java 中如此)。搜索或查看Wikipedia's entry 的确切原因。

    首先是程序的正确性。如果您的代码不是线程安全的(在多线程环境中),那么它就会损坏。正确性高于性能优化。

    要正确,您必须同步整个 getInstance 方法

    public static synchronized Singleton getInstance() {
       if (instance==null) ...
    }
    

    或静态初始化它

    private static final Singleton INSTANCE = new Singleton();
    

    【讨论】:

    • 如何解决 PMD 错误“使用块级别而不是方法级别同步”?
    • 是否可以使用静态块进行实例变量初始化而不是使用同步方法?静态块将在 jvm 第一次加载类时运行,这会是线程安全的吗?
    【解决方案2】:

    在网络爬虫中对数据库使用延迟初始化可能不值得。延迟初始化增加了复杂性和持续的速度冲击。一种合理的情况是很有可能永远不需要数据。此外,在交互式应用程序中,它可用于减少启动时间并提供速度的错觉

    对于像网络爬虫这样的非交互式应用程序,它肯定需要它的数据库立即存在,延迟初始化不适合。

    另一方面,网络爬虫很容易并行化,并且会从多线程中受益匪浅。使用它作为练习来掌握java.util.concurrent 库将是非常值得的。具体来说,查看ConcurrentHashMapConcurrentSkipListMap,,它们将允许多个线程读取和更新共享映射。

    当你摆脱惰性初始化时,最简单的单例模式是这样的:

    class Singleton {
    
      static final Singleton INSTANCE = new Singleton();
    
      private Singleton() { }
    
      ...
    
    }
    

    关键字final 是这里的关键。即使您为单例提供static“getter”而不是允许直接字段访问,但使单例final 有助于确保正确性并允许 JIT 编译器进行更积极的优化。

    【讨论】:

    • 我的回答基于以下事实:所引用的文章侧重于使用双重检查锁定来懒惰地初始化单例。这篇文章将是一个非常糟糕的指南。
    • 啊,是的,没错。无论如何,双重检查锁定已被破坏。
    【解决方案3】:

    如果您的生命取决于几微秒,那么我建议您将资源锁定优化到真正重要的地方。

    但在这种情况下,这里的关键字是爱好项目

    这意味着如果您同步了整个 getInstance() 方法,那么在 99.9% 的情况下都可以。我不建议以任何其他方式这样做。

    稍后,如果您通过 profiling 证明 getInstance() 同步是您项目的瓶颈,那么您可以继续优化并发。但我真的怀疑这会给你带来麻烦。

    杰奇!

    【讨论】:

      【解决方案4】:

      试试Bill Pugh 按需初始化持有人习语的解决方案。 该解决方案是跨不同 Java 编译器和虚拟机的最可移植的解决方案。 该解决方案是线程安全的,不需要特殊的语言结构(即 volatile 和/或同步)。

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

      【讨论】:

        【解决方案5】:

        正如 Joshua Bloch 在他的“有效 Java 2nd edition”一书中所说,我也同意单元素枚举类型是实现单例的最佳方式。

        public enum Singleton {
          INSTANCE;
        
          public void doSomething() { ... }
        }
        

        【讨论】:

        • 据我了解,该技术特别适用于可序列化的单例,而 OP 没有提及任何内容。不过,对于未来的读者来说,这里值得注意。
        【解决方案6】:

        如果您查看该文章的最底部,您会看到仅使用静态字段的建议。那将是我的倾向:你真的不需要惰性实例化(所以你不需要 getInstance() 既是访问器又是工厂方法)。您只想确保您拥有这些东西中的一件,而且只有一件。如果你真的需要全局访问这样的东西,我会使用that code sample towards the very bottom

        class Singleton
        {
          private Vector v;
          private boolean inUse;
          private static Singleton instance = new Singleton();
        
          private Singleton()
          {
            v = new Vector();
            inUse = true;
            //...
          }
        
          public static Singleton getInstance()
          {
            return instance;
          }
        }
        

        请注意,Singleton 现在是在安装静态字段期间构建的。这应该可行,并且不会面临潜在的错误同步的线程风险。

        说了这么多,也许您真正需要的是现代 JDK 中可用的线程安全数据结构之一。例如,我是ConcurrentHashMap 的忠实粉丝:线程安全加上我不必编写代码(FTW!)。

        【讨论】:

        • 这种模式被称为“静态初始化器”
        • 是的,不过,我一直在尝试使用原始词汇。
        • 是的,这可能就是这样做的方法。但是现在我在想为什么不在我的线程蜘蛛类中创建一个静态成员 ConcurrentHashMap 呢?我想我的问题是,这是一种方法,有更好的方法吗?
        • HashMap 非常引人注目。它简单,快速,附带 JDK,简单简单 ;-)。对于更具体的指导,我认为我们需要更多关于您想要做什么的信息(这可能超出了这个问题的范围)。
        【解决方案7】:

        为什么不创建一个作为依赖注入传递给每个线程的数据结构。这样你就不需要单例了。您仍然需要确保线程安全。

        【讨论】:

          【解决方案8】:

          您引用的文章仅讨论了如何创建单例对象,在这种情况下可能是一个集合,是线程安全的。您还需要一个线程安全的集合,以便集合操作也能按预期工作。确保单例中的底层集合是同步的,可能使用ConcurrentHashMap

          【讨论】:

          • 只有当集合暴露在单例之外时才适用。如果不是,那么没有理由担心它。
          • @Jeach -- 不正确。要么是集合需要同步,要么是你公开的与之交互的方法需要同步。正是因为您只有一个实例,所以您需要注意确保一次只有一个线程处于临界区(例如在这种情况下更新与对象相关的计数)。仅使用同步集合比在方法中实现所有锁定代码要容易得多。
          【解决方案9】:

          查看这篇文章Implementing the Singleton Pattern in C#

          public sealed class Singleton
          {
              Singleton()
              {
              }
          
              public static Singleton Instance
              {
                  get
                  {
                      return Nested.instance;
                  }
              }
          
              class Nested
              {
                  // Explicit static constructor to tell C# compiler
                  // not to mark type as beforefieldinit
                  static Nested()
                  {
                  }
          
                  internal static readonly Singleton instance = new Singleton();
              }
          }
          

          【讨论】:

            【解决方案10】:

            怎么样:

            public static Singleton getInstance() {
              if (instance == null) {
                synchronize(Singleton.class) {
                  if (instance == null) {
                     instance = new Singleton();
                  }
                }
              }
            
              return instance;
            }
            

            【讨论】:

            • 在第 3 行,我认为您的意思是 Singleton.class。如果您将引用从“singleton”重命名为“instance”,您的示例可能会更清晰。
            • 你是对的。为什么这些编辑框没有代码完成:)
            • 双重检查锁模式已被证明被打破。
            猜你喜欢
            • 2016-07-08
            • 2011-11-06
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-06-17
            • 1970-01-01
            • 1970-01-01
            • 2012-02-05
            相关资源
            最近更新 更多