#
单例设计模式
单例模式主要是为了避免因为创建了多个实例造成资源的浪费,且多个实例由于多次调用容易导致结果出现错误,而使用单例模式能够保证整个应用中有且只有一个实例。
#
1.保证对象的唯一性
- 不允许其他程序new对象
- 在该类中创建对象
- 对外提供一个可以让其他程序获取该对象的方法
#
2.实现单例模式
- 构造函数私有化
- 通过new在本类中创建一个对象
- 定义一个公共方法,将该类创建的对象返回
#
3.代码实现
#
饿汉式
含义:类加载的时候就创建单例对象,不管是否会用到
特点:
- 写法简单,线程安全(因为类加载的时候就初始化了)
- 如果实例始终没有被用到,可能造成内存浪费
public class Singleton {
// 类加载时就创建
private static final Singleton instance = new Singleton();
// 私有构造方法
private Singleton() {}
// 获取实例的方法
public static Singleton getInstance() {
return instance;
}
}
#
懒汉式
含义:第一次调用getInstance方法的时候才创建单例对象。
特点:
- 延迟加载,节省资源
- 多线程下需要处理同步问题,否则可能出现多个实例。
- 保证线程安全,使用双重检查锁定
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
#
内部类
和饿汉式类似,但是使用内部类可以在需要的时候才创建单例对象。
public class Singleton {
private Singleton() {
}
private static class SingletonHodler {
private static Singleton singleton = new Singleton();
}
public static Singleton getInstance() {
return SingletonHodler.singleton;
}
}
#
枚举
使用枚举实现单例:
- 线程安全:枚举类在类加载时就会实例化,和饿汉式类似,天然线程安全。
- 防止反射破坏:反射无法通过 Constructor 强行创建新的枚举实例。
- 防止反序列化破坏:反序列化时,枚举会自动保证单例(不会创建新对象)。普通的单例类如果实现了 Serializable,在反序列化时会重新创建实例,而枚举不会。
public enum Singleton {
INSTANCE; // 唯一实例
// 你可以定义一些方法
public void doSomething() {
System.out.println("枚举单例方法被调用");
}
}
使用方式:
public class Test {
public static void main(String[] args) {
Singleton s1 = Singleton.INSTANCE;
Singleton s2 = Singleton.INSTANCE;
System.out.println(s1 == s2); // true,保证单例
s1.doSomething(); // 调用方法
}
}
#
注意
- 在双重检查锁定(DCL)实现中,使用volatile关键字修饰单例实例变量是必须的,因为它可以防止指令重排序问题,确保线程安全(在Java 5及以上版本的内存模型中)。
- 单例模式的设计初衷是为了确保一个类只有一个实例,并不是为了提高系统性能。事实上,对于不需要频繁创建和销毁的对象,我们有其他更合适的设计模式和优化方案。
- 饿汉式:线程安全且无需显式同步,但实例在类加载时初始化,不符合延迟加载。
- 懒汉式:线程安全且延迟加载,但使用synchronized方法,属于显式同步。
- 双重检查锁定加volatile:线程安全且延迟加载,但使用synchronized块,属于显式同步,不符合要求。
- 静态内部类单例:线程安全(由JVM类加载机制保证),延迟加载(通过静态内部类的加载机制实现,仅在首次调用getInstance时初始化实例),且无需显式同步(无synchronized代码)。
- 在 Java 中实现单例,要求:惰性加载;在高并发下基本零运行时同步开销;实现简单、易读;不特别关心反序列化或反射攻击。
- synchronized懒汉式:存在同步开销,高并发下性能差,不满足基本零运行时同步开销要求。
- 使用volatile的双重检查锁:支持惰性加载,高并发下仅首次创建有同步开销,之后基本零开销,但实现相对复杂(需volatile和双重检查),易读性较差
- 静态内部类Holder模式:支持惰性加载(类加载机制保证首次调用时初始化),无任何同步代码,高并发下零运行时同步开销,实现简洁易读,完美满足所有要求。
- 枚举实现单例:非惰性加载(枚举常量在类加载时初始化),不符合惰性加载要求。
- Spring框架中的bean默认是单例的,通过IOC容器来管理这些单例对象的生命周期。当使用@Autowired注解进行依赖注入时,容器会确保注入的是同一个单例对象,这完全符合单例模式的设计理念。