单例模式的实现方式[singleton pattern]

单例模式(Singleton pattern)

1 概念

单例模式属于创建型设计模式,提供了一种访问类的最佳方式。

单例模式要求类自己创建本类对象,且只能创建一个对象;这个类提供访问类的唯一方式;

即单例类的要求为:

  • 只能创建一个对象
  • 自己创建本类实例
  • 提供供其它类访问该类实例的方法

2 实现思路

  1. 私有化构造函数

  2. 将创建的对象保存下来

  3. 提供方法对象实例的方法

3 实现方式

3.1 懒汉式

3.1.1 实现

public class SingletonClass01 {
    private static SingletonClass01 singleClass;

    private SingletonClass01() {
    }

    public static SingletonClass01 getSingleClass() {
        if (singleClass == null) {
            singleClass = new SingletonClass01();
        }
        return singleClass;
    }
}

3.1.2 分析

上述代码只有在使用到的时候才会实例化对象,不使用时不占用资源;但是这种实现方式是线程不安全的,如果多个方法同时到达 singleClass == null 语句,可能都得到true的结果,导致创建多个对象,违反了单例的概念。

所以为实现线程安全,对get方法进行加锁

public static synchronized SingletonClass01 getSingleClass() {
        if (singleClass == null) {
            singleClass = new SingletonClass01();
        }
        return singleClass;
    }

3.2 饿汉式

3.2.1 实现

public class HungrySingle {
    private static final HungrySingle single = new HungrySingle();

    private HungrySingle() {
    }

    private static HungrySingle getInstance() {
        return single;
    }
}

3.2.2 分析

本实现方法在类加载阶段即创建类的实例,与懒汉式方式相反,提前创建了对象,如果该类未被使用,则造成资源浪费。但是也因为提前创建了实例,不会出现single为空的情况,所以是线程安全的。

3.3 双重校验锁

3.3.1 实现

public class DoubleCheckLock {
    private static volatile DoubleCheckLock single;

    private DoubleCheckLock() {

    }

    public static DoubleCheckLock getInstance() {
        if (single == null) {
            synchronized (DoubleCheckLock.class) {
                if (single == null) {
                    single = new DoubleCheckLock();
                }
            }
        }
        return single;
    }
}

3.3.2 分析

为什么在加锁之后需要在判空

如果加锁之后不判空,是把所有线程阻塞在 single = new DoubleCheckLock() 中,锁被释放后,下一个线程仍旧会创建对象,并没有达到预期效果只创建一个实例。

为什么要使用volatile关键字

single = new DoubleCheckLock() 这一段代码有三个步骤并非原子的

  1. 分配内存空间
  2. 初始化对象
  3. 将对象地址赋给single

JVM在多线程的情况下可能会对指令进行重排,先1在3后2,从而导致最终赋给single的对象的地址但·并没有初始化对象,volatile禁止指令重排序的特性可以解决这个问题。

3.4 静态内部类

3.4.1 实现

public class StaticInnerClass {
    private StaticInnerClass() {
    }

    private static class InstanceHolder {
        private static final StaticInnerClass single = new StaticInnerClass();
    }

    public static StaticInnerClass getInstance() {
        return InstanceHolder.single;
    }
}

3.4.2 分析

这种写法既可以是线程安全的且是延迟加载的。

当类加载时InstanceHolder并没有被加载进内存,而是在调用getInstance方法时执行 InstanceHolder.single 才加载对single进行初始化;而初始化的过程JVM对此提供了线程安全的保护。

3.5 枚举

3.5.1 实现

public class Singleton {
    private Singleton(){
    }
    public static enum SingletonEnum {
        SINGLETON;
        private Singleton instance = null;
        private SingletonEnum(){
            instance = new Singleton();
        }
        public Singleton getInstance(){
            return instance;
        }
    }
}

3.5.2 分析

实现单例模式的最佳方式

利用枚举的特性来解决线程安全和单一实例的问题,可以防止反射和反序列化对单例的破坏。

反射不能创建类的实例的原因是在反射newInstance时会对类的类型进行判断,如果是enum,则不予创建对象。

4 使用场景

如果一个全局使用的类频繁地被创建与销毁可以考虑使用单例模式,如果想控制实例数目,节省系统资源可以考虑使用单例模式。

  1. 多线程池中的线程池
  2. 数据库的连接池
  3. 应用程序日志
  4. 全局唯一弹窗,如点击一个按钮弹窗提示信息,只要出现一个给用户看即可,重复弹窗过多也会造成资源浪费,同时可能导致原来的变量引用的地址被改变,之前的弹窗无法手动控制销毁的问题。

5 总结

  1. 单例模式提供了对唯一实例的受控访问,在内存中只存在一个实例可以节约系统资源,在特定条件下可以提高系统性能。

  2. 但是单例模式也有一定的缺点,它违背了单一职责原则,不易于扩展,不能保存彼此的状态。、

  3. 实现单例模式的方式主要有懒汉式、饿汉式、双重校验锁、静态内部类、枚举实现,他们在延迟性、线程安全上的特点、原理及实现方式有所不同,其中枚举被大多数书籍列为实现单例的最佳方式,因为它不仅可以满足上述要求,还可以防止反序列化、反射对单例的破坏。

In case I don’t see you, good afternoon, good evening and good night.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值