以下哪个选项不是单例模式的优点_趣谈设计模式之单例模式

1

什么是单例模式

单例模式(Singleton pattern),在任何情况下,保证只有一个实例,并提供一个全局访问点,属于创建型设计模式。

单例模式用途

在Java中,单例模式可以保证在一个JVM中只存在单一实例。单例模式有如下常用的场景:

  • 一些需要频繁创建的类,单例化减少系统压力;
  • 某些类创建实例时占用资源较多,或实例化耗时较长,且经常使用;
  • 对资源进行频繁操作的对象,如线程池、缓存、数据库连接池和文件读取类等。
具体来说,我们会在日常开发中遇到这样一些单例对象:线程池、连接池、缓存、日志对象、一些硬件设备的驱动程序、资源加载对象和全局管理类对象等。

单例模式类图

f2852a28a1556b7edb632bae3fcfb121.png

这里以饿汉式为例给出的类图,下面将详细给出代码。

单例模式的几种实现方法

Singleton单例类的构造函数是private的,这是为了禁止外部代码调用构造函数创建实例。构造函数私有化后,我们一般会提供public的getInstance方法,用于获取实例。单例模式的常见实现,这里我总结如下:

5fec856499b4acd4fcab99f83859ba2d.png

下面我将依次实现,并尽可能说明每种方式的优劣。

饿汉式——经典写法

/** * 饿汉模式经典写法,简单实用,较为通用 */public class HungrySingleton {    private static final HungrySingleton instance = new HungrySingleton();    private HungrySingleton() {    }    public static HungrySingleton getInstance() {        return instance;    }}

饿汉式——静态代码块写法

/** * 饿汉式的静态代码块写法 */public class HungryStaticSingleton {    private static final HungryStaticSingleton instance;        static {        instance = new HungryStaticSingleton();    }        private HungryStaticSingleton() {    }    public static HungryStaticSingleton getInstance() {        return instance;    }}

饿汉式总结:

  • 优点:线程安全,执行效率高,简单易于实现,适用于单例对象较少的情况;
  • 缺点:实例的初始化不会考虑系统是否需要,在类加载的时候就进行实例化。在系统存在较多单例对象时候,会带来内存资源浪费。

懒汉式单例

这里直接给出懒汉式的双重检查机制(DCL)的写法,其中的演化过程以及所解决的问题将简化描述,其详细的细节可查阅相关资料。
/** * 这就是DCL双重检查机制,其是线程安全的 */public class LazyDoubleCheckSingleton3 {    private volatile static LazyDoubleCheckSingleton3 instance;    private LazyDoubleCheckSingleton3(){}    public static LazyDoubleCheckSingleton3 getInstance() {        // 第一个判断是否要等待获取锁,如果对象已经存在,则直接返回,大大提高了因获取锁带来的性能消耗        if (instance == null) {            synchronized (LazyDoubleCheckSingleton3.class) {                // 检查是否要重新创建实例                if (instance == null) {                    instance = new LazyDoubleCheckSingleton3();                }            }        }        return instance;    }}
关于上述双重检查机制的单例写法有以下几点说明:
  • synchronized 同步代码块尽可能缩小同步范围,提高性能;
  • 第一个判断:检查是否要等待获取锁,如果对象已经存在,则直接返回,大大提高了因获取锁带来的性能消耗;
  • 第二个判断:检查是否要重新创建实例,防止多个线程并发创建;
  • volatile 关键字解决new LazyDoubleCheckSingleton3()过程中的指令重排,防止实例instance获取到未初始化的对象;
  • 基于volatile的解决方案需要JDK5或者更高版本(因为从JDK5开始使用新的JSR-133内存模型规范,这个规范增强了volatile的语义)。

上述双重检查机制的volatile的补充说明(选读):

其代码中创建实例的部分:instance = new LazyDoubleCheckSingleton3();,这一步可以分解为如下三步来执行:

memory = allocate(); // 1: 分配对象的内存空间

ctorInstance(memory); // 2: 初始化对象

instance = memory; // 3: 设置instance指向刚分配的内存地址

上面3行伪代码中的2和3之间,可能会被重排序(在一些JIT编译器上)。重排序后的伪代码可能是这样的:memory = allocate(); // 1: 分配对象的内存空间 

instance = memory; // 3: 设置instance指向刚分配的内存地址 // 注意,此时对象还没有被初始化 ctorInstance(memory); // 2: 初始化对象 

重排序以后,就会有其他线程可能获取到未初始化完成的实例,带来风险。

静态内部类实现单例

JVM在类的初始化阶段,会执行类的初始化。在执行类的初始化期间,JVM会去获取一个锁。这个锁可以同步多个线程对同一个类的初始化。基于这个特性,可以实现静态内部类的单例。

/** * 静态内部类实现单例 * 这种写法由JVM的类加载机制保证实例的线程安全,以及延迟加载的特性;而且避免了synchronized带来的性能消耗 */public class LazyStaticInnerClassSingleton {    private LazyStaticInnerClassSingleton(){}    public static LazyStaticInnerClassSingleton getInstance() {        return LazySingletonHolder.instance;    }    private static class LazySingletonHolder {        private static final LazyStaticInnerClassSingleton instance = new LazyStaticInnerClassSingleton();    }}

静态内部类写法的总结:

  • 优点:延迟加载,内部类只有在使用到该实例的时候才会加载;避免了synchronized带来的性能问题;
  • 缺点:不能解决反射和序列化的破坏,这也是饿汉式和懒汉式写法的问题。

枚举单例

枚举单例是一种较为完备的方案,其可以解决反射和序列化对单例的破坏。

public enum EnumSingleton {    INSTANCE;        // 枚举的属性    private Object data;    public Object getData() {        return data;    }    public void setData(Object data) {        this.data = data;    }    public static EnumSingleton getInstance() {        return INSTANCE;    }}
在稍后的文章中,将详细叙说下枚举单例的应用。

总结

在实际开发中,我们一般推荐饿汉式的经典写法,静态内部类的写法。关于单列模式的多种写法以及测试案例,可以在如下地址找到:

https://github.com/SmilingBug/design-patterns-java

参考引用

[1] 谭勇德,设计模式就该这么学,中国工信出版社

[2] 【日】结城浩,图解设计模式

[3] 高洪岩,Java多线程编程核心技术,机械工业出版社

[4] 方腾飞、魏鹏、程晓明,Java并发编程的艺术

[5]  JosHua Bloch,Effective Java

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值