如何以正确的姿势写单例

作者: MrCoding | 来源:发表于2016-02-04 11:18 被阅读582次
    Forever A Singleton

    本文定位于理解和总结《Effective Java》的所讲内容,而不是翻译,因此不当之处,还请广大网友指出。

    关于单例

    单例是指仅需要被实例化一次的类,在编程实践中往往用于实现那些仅需要一个对象的系统模块。单例模式的实现有很多种,具体可参见这篇博客,单例模式对其具体实现的基本要求只有两个:线程安全有且仅有一个实例

    本文并不讨论单例模式本身的优劣,尽管这类讨论一直都有并将持续下去,实际上,单例模式广泛应用于编程实践,本文的主题是如何优雅地实现单例。

    线程安全

    线程安全分为两部分,创建和获取实例时的线程安全以及调用单例其他方法时的线程安全,后一部分的线程安全由客户端程序员保证,在此不做讨论,实际上,创建和获取实例时的线程安全,目前大多数的实现是可以保证的,比如:

    // Singleton with public final field
    public class Elvis {
        public static final Elvis INSTANCE = new Elvis();
        private Elvis() { ... }
        // other methods
        public void leaveTheBuilding() { ... }    
    }
    

    类加载过程的线程安全性保证了上述实现中实例创建过程的线程安全,而获取该实例过程的线程安全性是显而易见的(由public关键字决定)。
    实际上,对于大多数的单例实现形式而言,线程安全是容易保证的,而有且仅有一个实例这个要求却难以保证,往往需要增加一些特殊处理。比如上述实现中,仍然可以通过反射的方法来创建一个新的实例;或者上述类在实现Serializable接口之后,在反序列化该类时,可以获取一个新的实例。

    关于如何增加“特殊处理”来保证上述实现中有且只有一个实例,将会在后续的文章中给出。

    有且仅有一个实例

    很多人认为线程安全比有且仅有一个实例更难保证,其实不然。要保证单例模式有且仅有一个实例,除了将构造器设置为private之外,还要求实现类能够抵抗反射攻击以及反序列化攻击(在反序列化过程中产生新的实例)。在Java 1.5之后增加的枚举类型天然的符合上述要求,因此利用枚举来实现单例模式是一个很好的方案,遗憾的是,目前该方法并没有被广泛采用。其实现非常简单,如下:

    // Enum singleton - the preferred approach
    public enum Elvis {
        INSTANCE;
        public void leaveTheBuilding(){ ... }
    }
    
    • 枚举构造器的访问修饰符只能是private
      这一特性保证了无法直接使用new关键字创建枚举对象,既除了枚举中定义的对象之外,外部程序无法创建对象。
    • 不能通过反射调用构造器创建新的枚举实例
      通过反射调用枚举构造器时会抛出java.lang.IllegalArgumentException异常。
    • 枚举类型的反序列化过程不会产生新的实例。
      这一点由枚举类型本身保证,客户端程序员无需做任何特殊处理。

    当然,使用枚举来实现单例模式也是线程安全的,也是通过类加载过程的线程安全性来保证的。

    综上,在所有单例模式的实现中,枚举以最优雅的方式实现了线程安全,保证了有且仅有一个实例,并且使用起来非常简单,因此下次需要一个单例模式时,请考虑使用枚举。

    相关文章

      网友评论

        本文标题:如何以正确的姿势写单例

        本文链接:https://www.haomeiwen.com/subject/zgjekttx.html