【设计模式】一文搞懂单例模式

 

目录

1. 引言

2. 单例模式

3. 饿汉式单例模式

4. 懒汉式单例模式

4.1 懒汉式代码实现优化(加锁)

4.2 懒汉式代码实现优化(双重校验锁)

5. 静态内部类实现单例模式

6. 枚举实现单例模式

7. 破坏单例模式的问题

7.1 反射破坏单例模式

7.2 序列化破坏单例


 

1. 引言

在软件开发中,我们经常会遇到需要确保某个类只有一个实例存在的情况,在这种情况下,单例模式成为了一种强大而常用的设计模式。

想象一下,当我们需要访问应用程序的配置信息时,我们希望有一个唯一的配置对象来统一管理这些信息,这时,单例模式就能派上用场。

然而,单纯地使用单例模式并不足以解决所有问题,在实现单例模式时,我们需要考虑到线程安全性、延迟加载等因素,以确保其在不同情况下的正确性和性能。因此,本文将手把手地介绍如何实现单例模式,从简单的懒汉式到更加复杂的线程安全方案,帮助读者深入理解这一设计模式的原理和实现方式。

 

2. 单例模式

单例模式是一种创建型设计模式,它保证一个类仅有一个实例,并提供一个全局访问点来访问这个实例。单例模式有多种实现方式,其中包括饿汉式、懒汉式、懒汉式加同步锁、双重校验锁、静态内部类和枚举等六种方式。

 

3. 饿汉式单例模式

class HungrySingleton {
    // 在类加载时就创建实例并初始化
    private static final HungrySingleton instance = new HungrySingleton();

    // 私有化构造方法,防止外部new新的对象
    private HungrySingleton() {
    }

    // 提供一个公有的静态方法来获取实例,当使用到该方法时,直接获取instance,即预加载
    public static HungrySingleton getInstance() {
        return instance;
    }
}

优缺分析

优点:实现简单,不存在线程安全,实例在类加载时就已经创建好,性能较好。

缺点:因为在类加载时就创建了实例,即使在整个应用程序的生命周期中没有被使用,实例也会一直存在,可能会造成资源浪费。

 

4. 懒汉式单例模式

class LazySingleton{
    // 声明一个私有静态变量来保存实例
    private static LazySingleton instance;

    // 私有化构造方法,防止外部new新的对象
    private LazySingleton(){}

    // 提供一个公有的静态方法来获取实例,当使用到该方法时,才去创建instance,即懒加载
    public static LazySingleton getInstance(){
        if(instance==null){
            instance=new LazySingleton();
        }
        return instance;
    }
}

优缺分析

优点:懒汉式单例模式在需要时才创建实例,可以延迟对象的创建过程,节省资源并提高性能。

缺点:存在线程安全问题,可能会创建多个实例。

 

4.1 懒汉式代码实现优化(加锁)

为了解决上文提到的懒汉式单例模式的线程安全性问题,可以使用同步锁来确保在多线程环境下只创建一个实例。

class LazySingleton{
    // 声明一个私有静态变量来保存实例
    private static LazySingleton instance;

    // 私有化构造方法,防止外部new新的对象
    private LazySingleton(){}

    // 提供一个公有的静态方法来获取实例,当使用到该方法时,才去创建instance,即懒加载
    public static synchronized LazySingleton getInstance(){
        if(instance==null){
            instance=new LazySingleton();
        }
        return instance;
    }
}

或者这样加锁

class LazySingleton {
    // 声明一个私有静态变量来保存实例
    private static LazySingleton instance;

    // 私有化构造方法,防止外部new新的对象
    private LazySingleton() {
    }

    // 提供一个公有的静态方法来获取实例,当使用到该方法时,才去创建instance,即懒加载
    public static LazySingleton getInstance() {
        synchronized (LazySingleton.class) {
            if (instance == null) {
                instance = new LazySingleton();
            }
        }
        return instance;
    }
}

优缺分析

优点:解决了懒汉式单例模式的线程安全性问题。

缺点:加锁会造成性能损耗。

 

4.2 懒汉式代码实现优化(双重校验锁)

虽然使用了同步锁可以解决线程安全的问题,但是在多线程并发访问的情况下,仍然会有多个线程竞争同一把锁,可能会导致性能下降和线程阻塞,同时还有可能出现指令重排的问题,故使用双重校验锁进行优化。

class LazySingleton {
    // 声明一个私有静态变量来保存实例,使用volatile关键字,防止指令重排
    private static volatile LazySingleton instance;

    // 私有化构造方法,防止外部new新的对象
    private LazySingleton() {
    }

    // 提供一个公有的静态方法来获取实例,当使用到该方法时,才去创建instance,即懒加载
    public static LazySingleton getInstance() {
        if (instance == null) {
            synchronized (LazySingleton.class) {
                // 双重检查,防止多个线程同时通过第一个if语句
                if (instance == null) {
                    instance = new LazySingleton();
                }
            }
        }
        return instance;
    }
}

优缺分析

优点:采用了双重校验锁的方式,大部分情况下不需要进入同步代码块,提高了性能。

缺点:双重校验锁的实现相对比较复杂,包含了两次检查实例是否为null的逻辑,增加了代码的复杂性和理解难度。

 

5. 静态内部类实现单例模式

静态内部类实现的单例模式是一种比较优雅且安全的方式,它能够保证懒加载、线程安全,并且没有性能损耗。

class Singleton {
    // 私有化构造函数,防止外部实例化
    private Singleton() {
    }

    // 静态内部类,在需要时加载,保证了懒加载
    private static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton();
    }

    // 提供一个公有的静态方法来获取实例
    public static Singleton getInstance() {
        return SingletonInstance.INSTANCE;
    }
}

优缺分析

优点:具有懒加载、线程安全和高效性特点,同时避免了反序列化破坏单例的问题。

缺点:无法传递参数和被继承,并且需要特别注意序列化问题。

 

6. 枚举实现单例模式

枚举实现单例模式是一种简洁、安全且线程安全的方式,它充分利用了枚举类型的特性,保证了单例的唯一性和防止反序列化破坏单例的问题。

enum Singleton {
    // 枚举单例
    INSTANCE;

    // 可以在枚举类中添加其他方法
    public void Method() {
    }
}

优缺分析

优点:枚举实现的单例模式在类加载时就创建实例,保证了线程安全性,同时天然地避免了反序列化破坏单例的问题。

缺点:无法延迟加载实例和传递参数给构造函数。

 

7. 破坏单例模式的问题

7.1 反射破坏单例模式

反射可以破坏传统的单例模式实现,因为它可以通过私有构造函数来创建新的实例。这可能会导致单例模式的唯一性受到破坏,从而违反了单例模式的原则。我们可以在单例模式的私有构造函数中添加逻辑检查,抛出异常,防止反射创建新的实例。

6df3964977c94c5caa149f7fe3e1c59f.png

7.2 序列化破坏单例

序列化和反序列化可能会破坏传统的单例模式实现,因为反序列化过程中会通过构造函数创建新的实例,从而导致单例模式的唯一性受到破坏。我们可以在 readObject() 方法中返回已存在的单例实例,确保反序列化过程中不会创建新的实例。

1be0ffce12ba4347be4e1c8f4834aefe.png

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值