单例模式

单例模式

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供了一个全局访问点来访问该实例。

注意:

  1. 单例类只能有一个实例。
  2. 单例类必须自己创建自己的唯一实例。
  3. 单例类必须给所有其他对象提供这一实例。

1. 饿汉式(静态变量)

public class Singleton {
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return INSTANCE;
    }
}
  • 保证单例的点:静态变量在类加载时初始化,只初始化一次。
  • 线程安全:因为类加载本身就是线程安全的。

2. 饿汉式(静态代码块)

public class Singleton {
    private static final Singleton INSTANCE;

    static {
        INSTANCE = new Singleton();
    }

    private Singleton() {}

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

  • 保证单例的点:和上面一样,只是初始化写到了静态代码块里。

3. 懒汉式(线程安全同步方法 )

public class Singleton {
    private static Singleton instance;

    private Singleton4() {}

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

  • 保证单例的点:synchronized保证同一时刻只有一个线程能进入方法。
  • 缺点:每次都同步,性能差。

4. 双重检查锁(DCL)

public class Singleton {
    private static volatile Singleton instance; // volatile 一定要有

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {  // 还没创建则创建
            synchronized (Singleton.class) { // 加锁
                if (instance == null) { // 拿到锁的时候可能有其他线程先拿到锁已经创建了,这个时候再判断一次是否已经创建
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
  • 保证单例的点:

    • 外层第一次检查instance == null避免加锁开销;

    • 内层加锁后再检查一次,确保只有一个实例;

    • volatile禁止指令重排序,防止半初始化问题。

5. 静态内部类方式

public class Singleton {
    private Singleton() {}

    private static class Holder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return Holder.INSTANCE;
    }
}
  • 保证单例的点:

    • 类加载是懒加载的,Holder类不会初始化,直到调用getInstance();

    • JVM保证类初始化是线程安全的。

6. 枚举单例

public enum Singleton {
    INSTANCE;

    public void doSomething() {
        System.out.println("Doing something...");
    }
}
  • 保证单例的点:

    • JVM保证每个枚举实例在内存中只创建一次;

    • 防止反射攻击;

    • 防止反序列化破坏单例。

7. Spring容器管理的单例

在Spring里,默认情况下,Bean是单例的(@Scope("singleton")):

@Service
public class MyService {
}
  • 保证单例的点:

    • Spring在IOC容器初始化阶段实例化Bean,并放到单例池;

    • 后续通过名字拿对象,拿的都是同一个。

8. 单例模式的常见问题

8.1反射破坏单例

反射可以访问私有构造器,从而绕开正常流程,直接创建多个实例。

Singleton instance1 = Singleton.getInstance();

Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton instance2 = constructor.newInstance();

System.out.println(instance1 == instance2); // false!

可以在构造方法中加一个判断,防止二次实例化:

public class Singleton {
    private static final Singleton INSTANCE = new Singleton();
    private Singleton() {
        if (INSTANCE != null) {
            throw new RuntimeException("单例对象,禁止反射创建!");
        }
    }

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

这样即使通过反射调用,也会抛异常!

8.2 序列化破坏单例

如果单例对象实现了Serializable接口,在序列化 -> 反序列化过程中,会创建出一个新的对象实例。

Singleton instance1 = Singleton.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton.obj"));
oos.writeObject(instance1);

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton.obj"));
Singleton instance2 = (Singleton) ois.readObject();

System.out.println(instance1 == instance2); // false!

可以加一个 readResolve() 方法:

private Object readResolve() throws ObjectStreamException {
    return INSTANCE;
}

这样反序列化时不会新建对象,而是直接返回已有的实例!

规则:

  • readResolve()方法在ObjectInputStream读取对象后自动调用

  • 返回的对象替换掉反序列化出来的新对象

为什么推荐枚举单例

枚举类的实例创建由JVM保证,天然防止了:

  • 反射破坏

  • 反序列化破坏

  • 多线程破坏

总结

序号 实现方式 保证单例的机制 是否懒加载 线程安全 备注
1 饿汉式(静态变量) 类加载时创建 简单,推荐
2 饿汉式(静态代码块) 类加载时创建 可加复杂逻辑
3 懒汉式(线程安全同步方法) 延迟初始化+同步方法 性能差
4 懒汉式(DCL双重检查) volatile + 双检查锁 推荐,最经典
5 静态内部类 JVM加载机制 推荐,优雅
6 枚举单例 JVM保证枚举单例 最推荐,防反射和反序列化
7 Spring容器管理单例 Spring的IOC容器 可配置 Spring默认就是单例Bean
posted @ 2025-04-29 00:11  Eiffelzero  阅读(22)  评论(0)    收藏  举报