zoukankan      html  css  js  c++  java
  • 单例模式的几种写法的差异

    设计模式:单例模式的几种写法的差异

    1.单例模式的概念

    单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

    这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

    注意:

    • 1、单例类只能有一个实例。
    • 2、单例类必须自己创建自己的唯一实例。
    • 3、单例类必须给所有其他对象提供这一实例。

    2.单例模式的几种写法

    2.1饱汉模式

    ①基础的饱汉

    饱汉,即已经吃饱,不着急再吃,饿的时候再吃。所以他就先不初始化单例,等第一次使用的时

    候再初始化,即“懒加载”。

     

    饱汉模式的核心就是懒加载。好处是启动速度快、节省资源,一直到实例被第一次访问,才需要初始化单例;小坏处是写起来麻烦,大坏处是线程不安全,if语句存在竞态条件。

    单线程环境下,可以使用。

    ②饱汉-变种 1

    最粗暴的犯法是用synchronized关键字修饰getInstance()方法,这样能达到绝对的线程安全。

     

    变种1的好处是写起来简单,且绝对线程安全;坏处是并发性能极差,事实上完全退化到了串行。单例只需要初始化一次,但就算初始化以后,synchronized的锁也无法避开,从而getInstance()完全变成了串行操作。性能不敏感的场景建议使用。

    ③饱汉-变种 2

    变种2是“臭名昭著”的DCL 1.0。

    针对变种1中单例初始化后锁仍然无法避开的问题,变种2在变种1的外层又套了一层check,加上synchronized内层的check,即所谓“双重检查锁”(Double Check Lock,简称DCL)。

     

     

    变种2的核心是DCL,看起来变种2似乎已经达到了理想的效果:懒加载+线程安全。可惜的是,正如注释中所说,DCL仍然是线程不安全的,由于指令重排序,你可能会得到“半个对象”。

    ④饱汉-变种 3

    变种3专门针对变种2,可谓DCL 2.0

    针对变种3的“半个对象”问题,变种3在instance上增加了volatile关键字,volatile避免指令重排序。

     

    多线程环境下,变种3更适用于性能敏感的场景。

    2.2饿汉模式

    与饱汉相对,饿汉很饿,只想着尽早吃到。所以他就在最早的时机,即类加载时初始化单例,以后访问时直接返回即可。

     

    饿汉的好处是天生的线程安全(得益于类加载机制),写起来超级简单,使用时没有延迟;坏处是有可能造成资源浪费(如果类加载后就一直不使用单例的话)。

    单线程环境下,饿汉与饱汉在性能上没什么差别;但多线程环境下,由于饱汉需要加锁,饿汉的性能反而更优。

    2.3Holder模式

    我们既希望利用饿汉模式中静态变量的方便和线程安全;又希望通过懒加载规避资源浪费。Holder模式满足了这两点要求:核心仍然是静态变量,足够方便和线程安全;通过静态的Holder类持有真正实例,间接实现了懒加载。

     

    相对于饿汉模式,Holder模式仅增加了一个静态内部类的成本,与饱汉的变种3效果相当(略优),都是比较受欢迎的实现方式。

    2.4枚举模式

    将枚举的静态成员变量作为单例的实例:

     

    代码量比饿汉模式更少。但用户只能直接访问实例Singleton4.SINGLETON——事实上,这样的访问方式作为单例使用也是恰当的,只是牺牲了静态工厂方法的优点,如无法实现懒加载。

    通过反编译(jad)打开语法糖,就看到了枚举类型的本质,简化如下:

     

    本质上和饿汉模式相同,区别仅在于公有的静态成员变量。

    3.几种单例模式的总结

    上面的分析都忽略了反射和序列化的问题。通过反射或序列化,我们仍然能够访问到私有构造器,创建新的实例破坏单例模式。此时,只有枚举模式能天然防范这一问题。反射和序列化猴子还不太了解,但基本原理并不难,可以在其他模式上手动实现。

    下面继续忽略反射和序列化的问题,做个总结回味一下:

    实现方式

    关键点

    资源浪费

    线程安全

    多线程环境的性能足够优化

    基础饱汉

    懒加载

    -

    饱汉变种1

    懒加载、同步

    饱汉变种2

    懒加载、DCL

    -

    饱汉变种3

    懒加载、DCL、volatile

    饿汉

    静态变量初始化

    Holder

    静态变量初始化、holder

    枚举

    枚举本质、静态变量初始化

    经验之谈:一般情况下,不建议使用饱汉模式中的第 1 种和基本饱汉,建议使用饿汉模式。只有在要明确实现 lazy loading 效果时,才会使用Holder模式。如果涉及到反序列化创建对象时,可以尝试使用枚举方式。如果有其他特殊的需求,可以考虑使用饱汉模式中的第3种。

  • 相关阅读:
    困勉而行
    6.12
    js 实现表格筛选不请求后台数据
    VUE方法和函数汇总
    sql isnull用法
    js forEach的用法
    js _this.$nextTick 解决页面渲染问题
    element table 复选框单选
    js attr 追加属性
    C# 过滤器 验证页面权限
  • 原文地址:https://www.cnblogs.com/xxj-bigshow/p/7843497.html
Copyright © 2011-2022 走看看