【问题标题】:Thread safety in Singleton单例中的线程安全
【发布时间】:2011-02-24 03:10:41
【问题描述】:

我知道 Java 中的双重锁定已被破坏,那么在 Java 中使单例线程安全的最佳方法是什么?我首先想到的是:

class Singleton{
    private static Singleton instance;

    private Singleton(){}

    public static synchronized Singleton getInstance(){
        if(instance == null) instance = new Singleton();
        return instance;
    }
}

这行得通吗?如果是这样,这是最好的方法吗(我想这取决于具体情况,所以说明什么时候一种特定的技术是最好的,会很有用)

【问题讨论】:

  • return new Singleton()更改为instance = new Singleton()
  • 哎呀,好发现!未经测试就写...
  • 有人能告诉我为什么人们如此关注单例的延迟初始化吗? 他们的使用模式是,使用这个例子:Singleton.getInstance( )。在 99% 的情况下,应该没有其他理由在不调用其 getInstance 方法的情况下加载 Singleton 类。如果instancestatic final,那么它将在加载 Singleton 类时被初始化,并且是线程安全的。虽然LazyHolder 模式确实有效,但我认为这是不必要的矫枉过正和反模式,而且单例本身就是反模式。
  • 当然,如果它的创建成本很高,那么有时在负载上构建的成本太高,因为即使从未使用过对象也会留在堆上。
  • @罗伯特。这才是重点。加载单例类的唯一原因是立即调用getInstance。如果没有这个调用,就绝对不需要加载单例类。

标签: java multithreading singleton


【解决方案1】:

Josh Bloch 建议使用单元素 enum 类型来实现单例(请参阅Effective Java 2nd Edition,第 3 项:使用私有构造函数或枚举类型强制单例属性)。

有些人认为这是一种 hack,因为它没有清楚地传达意图,但它确实有效。

以下示例直接取自本书。

public enum Elvis {
   INSTANCE;

   public void leaveTheBuilding() { ... }
}

这是他的结束论点:

这种方法 [...] 更简洁,免费提供序列化机制,并提供针对多个实例化的铁定保证,即使面对复杂的序列化或反射攻击。虽然这种方法尚未被广泛采用,但单元素枚举类型是实现单例的最佳方式


开启enum 常量单例保证

JLS 8.9. Enums

枚举类型除了由其枚举常量定义的实例外,没有其他实例。尝试显式实例化枚举类型(第 15.9.1 节)是编译时错误。

Enum 中的final clone 方法确保永远不会克隆枚举常量,并且序列化机制的特殊处理确保永远不会由于反序列化而创建重复实例。禁止枚举类型的反射实例化。这四件事一起确保枚举类型的实例不存在超出枚举常量定义的实例。


关于延迟初始化

下面的sn-p:

public class LazyElvis {
    enum Elvis {
        THE_ONE;
        Elvis() {
            System.out.println("I'M STILL ALIVE!!!");
        }       
    }
    public static void main(String[] args) {
        System.out.println("La-dee-daaa...");
        System.out.println(Elvis.THE_ONE);
    }
}

产生以下输出:

La-dee-daaa...
I'M STILL ALIVE!!!
THE_ONE

如您所见,THE_ONE 常量直到第一次被访问时才通过构造函数实例化。

【讨论】:

  • 客户端对单例何时初始化有特定的控制权吗?
  • @Robert 我认为,因为 Elvis 是一个内部类,所以 Elvis.THE_ONE 在被调用之前不会被实例化。我认为这是内部类的一个特殊属性......如果 Elvis 是“常规”外部类,那么 Elvis.THE_ONE 将在应用程序启动后立即实例化。如果我错了,请有人纠正我。
  • @mangst:我做了一些实验,实际上我上面的测试根本不是结论性的,实际上可能会产生误导。枚举常量是惰性构造的,但比这要复杂一些。我建议将enum 与由aioobe 链接的按需初始化持有者习语结合起来。话虽如此,布洛赫认为延迟初始化并不是一个好的优化。它增加了不必要的复杂性,容易出错,并且很少提高性能。 Effective Java 2nd Edition,Item 71:明智地使用惰性初始化
  • @polygenelubricants “它比这更复杂一点”是什么意思?在调用 Elvis.THE_ONE 之前,不会运行 Elvis() 构造函数。这不是惰性初始化吗?
  • @mangst:当Elvis 有一个static 初始化块时运行构造函数。或者如果它有一个static 方法,并且你调用它,即使它与常量无关。这些只是我自己的非结构化实验的初步结果。
【解决方案2】:

我认为你的实现没有问题(除了单例监视器的锁可能被其他方法使用,出于其他原因,因此,不必要地阻止其他线程获取实例)。这可以通过引入额外的Object lock 来锁定来避免。

This Wikipedia article 建议另一种方法:

public class Something {
    private Something() {
    }

    private static class LazyHolder {
        private static final Something INSTANCE = new Something();
    }

    public static Something getInstance() {
        return LazyHolder.INSTANCE;
    }
}

来自文章:

此实现是一个性能良好的并发实现,适用于所有 Java 版本。
...
该实现依赖于 Java 虚拟机 (JVM) 中明确指定的执行初始化阶段;有关详细信息,请参阅 Java 语言规范 (JLS) 的第 12.4 节。

【讨论】:

  • 是的,这个更好。因为在您访问静态内部类的变量INSTANCE 之前,它不会被初始化(惰性)。而且初始化只有一次。谢谢。
【解决方案3】:

我的偏好是做:

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

    private Singleton(){}

    public Singleton instance(){
        return INSTANCE;
    }
 }

很少需要延迟初始化。您应该始终从急切初始化开始,只有在发现问题时才更改为延迟初始化。除非您已经测量并确定单例实例化是性能问题的罪魁祸首,否则请使用急切初始化。它更简单,性能更高。

您当然可以使用枚举,但我个人并不介意,因为与普通急切实例化相比的好处是安全性(防止反射攻击),而且大多数时候我的代码无论如何都容易受到此类攻击:p

【讨论】:

    【解决方案4】:

    Josh Bloch 推荐了 2 个解决方案:

    1) 更糟:

    class Singleton {
    
       public static Singleton instance = new Singleton();
    
       ...
    }
    

    2) 更好:

    public enum Singleton {
    
       INSTANCE;
    
       ...
    }
    

    【讨论】:

      【解决方案5】:

      您可以使用 wiki 中的这个 sn-p 代码

      public class Singleton {
         // Private constructor prevents instantiation from other classes
         private Singleton() {}
      
         /**
          * SingletonHolder is loaded on the first execution of Singleton.getInstance() 
          * or the first access to SingletonHolder.INSTANCE, not before.
          */
         private static class SingletonHolder { 
           private static final Singleton INSTANCE = new Singleton();
         }
      
         public static Singleton getInstance() {
           return SingletonHolder.INSTANCE;
         }
       }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-08-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多