【设计模式】单例模式

221 阅读5分钟

【设计模式】单例模式

1、什么是单例模式

单例模式(Singleton): 保证整个系统中一个类仅有一个对象实例,并提供一个可以访问它的全局访问点。

  • 为什么要设计单例模式?单例模式的优点体现在哪些方面?

    单例模式的设计保证了一个类在整个系统中同一时刻只有一个实例存在,主要被用于一个全局类的对象在多个地方被使用并且对象的状态是全局变化的场景下。同时,单例模式为系统资源的优化提供了很好的思路,频繁创建和销毁对象都会增加系统的资源消耗,而单例模式保障了整个系统只有一个对象能被使用,很好地节约了资源。

2、单例模式实现

🎍2.1 饿汉式(线程安全)

原理:在类加载时就创建实例

 //饿汉式
 public class HungrySingleton {
     private static HungrySingleton instance = new HungrySingleton();
     private HungrySingleton(){}
     public static HungrySingleton getInstance(){
         return instance;
     }
 }

类中直接定义全局的静态对象的实例并初始化,然后提供一个方法获取该实例对象。优点是实现简单,并且线程安全,缺点也很明显,比如在代码中没有使用这个对象也会创建出一个实例,可能会造成资源浪费。

🍔2.2 懒汉式(线程不安全)

原理:首次使用时才创建实例

public class LazySingleton {
    private static LazySingleton instance;
    
    private LazySingleton() {}
    
    public static LazySingleton getInstance() {
        if (instance == null) {
            // 多线程可能同时进入此区域
            instance = new LazySingleton();
        }
        return instance;
    }
}

做法和饿汉式基本差不多,只是在返回实例对象时加入判断,如果已经创建,直接返回创建好的实例对象,确保首次使用才创建。不过需要注意的是,在进行判断时,多线程情况下的其他线程也可能会进入判断,导致线程不安全。优点是延迟加载,需要用到才会创建,一旦创建就一直使用已经创建好的实例,改善了饿汉式的缺点。

🍪2.3 同步方法懒汉式 (线程安全)

原理:通过方法级同步保证线程安全

public class SyncMethodSingleton {
    private static SyncMethodSingleton instance;
    
    private SyncMethodSingleton() {}
    
    // 方法同步保证线程安全
    public static synchronized SyncMethodSingleton getInstance() {
        if (instance == null) {
            instance = new SyncMethodSingleton();
        }
        return instance;
    }
}

和懒汉式唯一的区别在于获取实例对象方法被synchronized 关键字修饰,相当于锁住整个类对象,同一时刻只允许一个线程进入方法,防止多线程情况下创建多个实例

  • 优点:实现简单且线程安全
  • 缺点:性能差(每次调用都同步)

🏇2.4 双重检查锁定

原理:减少同步范围 + volatile禁止指令重排序

public class DCLSingleton {
    // volatile保证可见性和禁止指令重排序
    private static volatile DCLSingleton instance;
    
    private DCLSingleton() {}
    
    public static DCLSingleton getInstance() {
        if (instance == null) { // 第一次检查(无锁)
            synchronized (DCLSingleton.class) {
                if (instance == null) { // 第二次检查(有锁)
                    instance = new DCLSingleton();
                }
            }
        }
        return instance;
    }
}

第一次if(instance==null):为了提高代码的执行效率,由于单例模式只要一次创建实例即可,所以当创建了一个实例之后,再次调用getInstance方法就不同进入同步代码块,不用竞争锁。

第二次if(instance==null):这个校验时防止二次创建实例。

  • 优点:高性能线程安全

  • 缺点:实现较复杂

1.为什么用 volatile
  • 禁止指令重排序

    instance = new Singleton()实际分为三步:

    1.分配内存空间

    2.初始化对象

    3.将引用指向内存地址

    若无 volatile,JVM 可能重排序为 1→3→2。此时其他线程可能拿到未初始化的对象(instance != null但对象不完整)。

  • 内存可见性:确保所有线程看到最新的 instance值。

2.双重检查的必要性
  • 第一次检查:避免每次访问都加锁(性能优化)。
  • 第二次检查:防止多个线程同时通过第一次检查后重复创建实例。

🚗2.5 静态内部类(线程安全)

原理:利用类加载机制保证线程安全

public class HolderSingleton {
    private HolderSingleton() {}
    
    // 静态内部类
    private static class SingletonHolder {
        private static final HolderSingleton INSTANCE = new HolderSingleton();
    }
    
    public static HolderSingleton getInstance() {
        // 首次调用时加载内部类
        return SingletonHolder.INSTANCE;
    }
}

静态内部类方式利用JVM类加载机制确保线程安全,同时实现懒加载特性(延迟初始化)。核心原理是:静态内部类不会在外部类加载时初始化,只有在显式调用时才会加载

  • 优点:延迟加载 + 无同步开销

  • 缺点:无法防止反射攻击

// 获取私有构造函数
Constructor<HolderSingleton> constructor = HolderSingleton.class.getDeclaredConstructor();
// 设置访问权限
constructor.setAccessible(true);

🎠2.6 枚举单例

原理:使用一个只包含单个元素的枚举类型来实现单例模式。

public enum EnumSingleton {
    // 唯一实例
    INSTANCE;
    
    // 添加逻辑方法
    public void doSomething() {
        System.out.println("枚举单例方法");
    }
}

// 使用方式
EnumSingleton.INSTANCE.doSomething();
  • 优点:绝对单例(防止反射/序列化破坏)

  • 缺点:不够灵活

绝对单例原理:
  1. 全局唯一实例:每个枚举常量都是public static final
  2. 线程安全:由JVM在类加载时完成实例化
  3. 防反射攻击:JDK底层禁止通过反射创建枚举实例
  4. 防序列化破坏:枚举使用特殊机制保证反序列化返回唯一实例

普通单例被序列化再反序列化时,会绕过构造函数创建新实例

枚举的防御机制:枚举通过双重防御体系保证唯一性

各实现方式对比

实现方式延迟加载线程安全性能防反射防序列化
饿汉式
懒汉式
同步方法懒汉式低(频繁加锁)
双重检查锁定较高
静态内部类
枚举