23种设计模式之单例模式

单例设计模式

1、定义

单例模式:确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一实例。

单例模式的三个要点:

  • 只有一个实例
  • 这个类必须自行创建这个实例
  • 这个类必须自行向整个系统提供这个实例

2、单例模式的七种实现方式

2.1 懒汉模式

单例实例在第一次使用的时候被创建

这种方式是线程不安全的,多个线程同时调用,可能会创建多个对象

示例代码

public class SingletonDemo1 {

    private SingletonDemo1() {}

    private static SingletonDemo1 instance = null;

    public static SingletonDemo1 getInstance() {
        if (instance == null) {
            instance = new SingletonDemo1();
        }
        return instance;
    }
}
2.2 饿汉模式

在类装载的时候被创建,是实现起来最简单的单例类

这种方式是线程安全的

示例代码

public class SingletonDemo2 {

    private SingletonDemo2() {}

    private static SingletonDemo2 instance = new SingletonDemo2();

    public static SingletonDemo2 getInstance() {
        return instance;
    }
}

以上两种实现方式是懒汉模式与饿汉模式最简单的实现。

对比这两种模式可以发现:

  • 饿汉模式在类被加载时就将自己实例化,无需考虑多线程问题,在调用速度和反应时间方面要优于懒汉模式。
  • 饿汉模式在系统加载时需要创建单例对象,因此加载时间会更长
  • 懒汉模式第一次使用时创建,无需一直占用系统资源,实现了延迟加载
  • 懒汉模式有多线程问题
2.3懒汉模式创建实例时加锁

为解决懒汉模式的线程安全问题,可以在getInstance()方法前增加synchronized关键字

示例代码

public class SingletonDemo3 {

    private SingletonDemo3() {}

    private static SingletonDemo3 instance = null;

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

这个类是线程安全的,但不推荐这种写法。因为每次调用getInstance()都要加锁,会使系统性能大大降低。

2.4 懒汉模式双重检查锁(Double-Check Locking)

为解决2.3中的性能问题,可以优化锁的粒度

示例代码

public class SingletonDemo4 {

    private SingletonDemo4() {}

    private static SingletonDemo4 instance = null;

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

问题貌似得到了解决,但事实并非如此!!!这种实现方式存在线程安全问题。

我们对线程安全问题进行分析:

当执行instance = new SingletonExample4();这行代码时,CPU会执行如下指令:

1.memory = allocate() 分配对象的内存空间

2.ctorInstance() 初始化对象

3.instance = memory 设置instance指向刚分配的内存

单纯执行以上三步没啥问题,但是在多线程情况下,可能会发生指令重排序

假设目前有两个线程A和B同时执行getInstance()方法,A线程执行到instance = new SingletonExample4(); B线程刚执行到第一个 if (instance == null){处,如果按照1.3.2的顺序,假设线程A执行到3.instance = memory 设置instance指向刚分配的内存,此时,线程B判断instance已经有值,就会直接return instance;而实际上,线程A还未执行2.ctorInstance() 初始化对象,也就是说线程B拿到的instance对象还未进行初始化,这个未初始化的instance对象一旦被线程B使用,就会出现问题。

2.5 volatile+Double-Check Locking

提到解决指令重排序造成的线程安全问题,我们首先应该想到的就是volatile关键字

示例代码

public class SingletonDemo5 {

    private SingletonDemo5() {}

    private static SingletonDemo5 instance = null;

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

被volatile修饰的成员变量是线程可见的,可解决线程安全问题。

这种实现方式只能在JDK1.5及以上版本中才能正确执行。volatile会导致系统的运行效率降低,因此这种实现方式并不完美。

2.6 使用静态内部类实现

为解决上述五种实现方式存在的线程安全或性能问题,使用静态内部类来实现单例模式。

示例代码

public class SingletonDemo6 {

    private SingletonDemo6() {}

    private static class HolderClass {
        private final static SingletonDemo6 instance = new SingletonDemo6();
    }

    public static SingletonDemo6 getInstance() {
        return HolderClass.instance;
    }
}

这是一种饿汉模式,这种方式由Java虚拟机来保证其线程安全性,确保该成员变量只能初始化一次。由于getInstance()方法没有加锁,因此不会有性能问题。

2.7 枚举方式进行实例化

示例代码

public class SingletonDemo7 {

    private SingletonDemo7(){}

    public static SingletonDemo7 getInstance(){
        return Singleton.INSTANCE.getInstance();
    }

    private enum Singleton{
        INSTANCE;
        private SingletonDemo7 singleton;

        //JVM保证这个方法绝对只调用一次
        Singleton(){
            singleton = new SingletonDemo7();
        }
        public SingletonDemo7 getInstance(){
            return singleton;
        }
    }
}

这种方式是线程最安全的方法

3、单例模式优缺点

3.1 优点
  • 提供了对唯一实例的受控访问。因为单例类封装了它的唯一实例,所以可以严格控制客户访问它的方式和方法
  • 节约系统资源
3.2 缺点
  • 没有抽象层,所以很难进行扩展
  • 一定程度上违背了单一职责原则
  • 长时间不用可能会被当成垃圾回收,导致共享的单例对象状态的丢失

4、适用场景

开发工具类库中的很多工具类都应用了单例模式,比如线程池、缓存、ID生成器、日志对象、网站的全局计数器等,它们都只需要创建一个对象。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值