单例模式
一、单例模式的定义
单例模式(Singleton Pattern)是一种常见的软件设计模式,确保一个类只有一个实例存在,并提供一个全局访问点来获取该实例。
二、单例模式的实现方式
1.懒汉式单例
java
public class LazySingleton {
private static LazySingleton instance; // 静态变量存储唯一实例
private LazySingleton() { // 私有构造函数,防止外部创建实例
System.out.println("懒汉式单例构造函数被调用");
}
public static LazySingleton getInstance() { // 获取实例的静态方法
if (instance == null) { // 如果实例尚未创建
instance = new LazySingleton(); // 创建实例
System.out.println("懒汉式单例实例首次创建");
}
return instance; // 返回实例
}
}
在懒汉式中,只有在第一次调用 getInstance
方法时才创建实例。
2.饿汉式单例
java
public class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton(); // 直接创建并初始化唯一实例
private EagerSingleton() { // 私有构造函数
System.out.println("饿汉式单例构造函数被调用");
}
public static EagerSingleton getInstance() { // 获取实例的静态方法
System.out.println("饿汉式单例实例获取");
return instance;
}
}
饿汉式在类加载时就创建了实例。
3.双重检验锁(Double Check Lock,DCL)
java
private volatile static Singleton singleton; // 使用volatile修饰,保证线程可见性
private Singleton (){} // 私有构造函数,防止外部直接实例化
/**
* 获取单例实例的方法
* @return 单例对象
*/
public static Singleton getInstance() {
if (singleton == null) { // 第一次检查,避免不必要的同步
synchronized (Singleton.class) { // 同步锁,保证线程安全
if (singleton == null) { // 第二次检查,确保在同步块内只创建一次实例
singleton = new Singleton(); // 创建单例实例
}
}
}
return singleton; // 返回单例实例
}
DCL 模式的突出特点在于,它成功地在确保线程安全的基础上,于多线程环境中依然能够维持出色的性能表现。通过巧妙地进行两次 singleton
是否为 null
的判断,有效地规避了不必要的同步操作所带来的性能损耗。
4.静态内部类
java
private static class SingletonHolder { // 静态内部类
private static final Singleton INSTANCE = new Singleton(); // 在内部类中创建单例实例
}
private Singleton (){} // 私有构造函数,防止外部直接实例化
/**
* 获取单例实例的方法
* @return 单例对象
*/
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE; // 直接返回静态内部类中的单例实例
}
它专门适用于静态域的场景。巧妙地借助了 Java 的类加载机制,仅在实际需要获取实例时才对静态内部类进行加载,从而顺利实现了延迟初始化的效果,同时也切实保障了线程的安全性。
5.枚举
java
public enum Singleton { // 定义枚举类型的单例
INSTANCE; // 唯一的枚举值即单例实例
}
它能够自动支持序列化机制,并且从根本上杜绝了多次实例化的可能性,是一种简洁、高效且极为可靠的单例实现手段。
三、破坏单例的情况及解决方法
-
反射破坏单例
- 问题:通过反射可以绕过私有构造函数的限制创建新的实例。
- 解决:在构造函数中进行判断,如果已经存在实例,抛出异常。
java/** * 四、破坏单例的情况及解决方法 */ /** * 反射破坏单例的解决示例 * 问题:通过反射可以绕过私有构造函数的限制创建新的实例。 * 解决:在构造函数中进行判断,如果已经存在实例,抛出异常。 */ public class SafeSingleton { private static SafeSingleton instance; // 静态变量存储唯一实例 /** * 私有构造函数 * 在构造函数中检查是否已有实例存在,若有则抛出运行时异常 */ private SafeSingleton() { if (instance!= null) { throw new RuntimeException("单例模式,禁止通过反射创建多个实例!"); } } /** * 获取单例实例的方法 * 如果实例不存在则创建,存在则直接返回 * @return 单例对象 */ public static SafeSingleton getInstance() { if (instance == null) { instance = new SafeSingleton(); } return instance; } } /** * 序列化和反序列化破坏单例的解决示例 * 问题:序列化后再反序列化可能创建新的实例。 * 解决:实现 readResolve 方法返回已有的实例。 */ public class SerializableSingleton implements Serializable { private static final SerializableSingleton instance = new SerializableSingleton(); // 初始化唯一实例 /** * 私有构造函数 */ private SerializableSingleton() {} /** * 获取单例实例的方法 * @return 单例对象 */ public static SerializableSingleton getInstance() { return instance; } /** * 在反序列化时调用,返回已有的实例 * @return 已有的单例实例 */ private Object readResolve() { return instance; } } /** * 克隆破坏单例的解决示例 * 问题:若单例类实现了 Cloneable 接口,通过克隆可能创建新实例。 * 解决:在 clone 方法中抛出异常或返回已有实例。 */ public class CloneableSingleton implements Cloneable { private static CloneableSingleton instance = new CloneableSingleton(); // 存储唯一实例 /** * 私有构造函数 */ private CloneableSingleton() {} /** * 获取单例实例的方法 * @return 单例对象 */ public static CloneableSingleton getInstance() { return instance; } /** * 重写 clone 方法,抛出不支持克隆的异常 * @return 抛出异常,禁止克隆 * @throws CloneNotSupportedException 不支持克隆异常 */ @Override protected Object clone() throws CloneNotSupportedException { throw new CloneNotSupportedException("单例模式,禁止克隆!"); } }
四、总结
适用场景 :
单例模式适用于在任何情况下都绝对只需要一个实例的情况,例如
ServletContext
、ServletConfig
、ApplicationContext
、DBPool
、ThreadPool
等。优点:
- 在内存中仅存在一个实例,显著降低了内存开销。
- 能够有效避免对资源的多重占用,保证资源的合理分配和使用。
- 设定了全局访问点,从而实现了对访问的严格管控。
缺点:
- 没有提供接口,导致扩展较为困难。
- 若要对单例对象进行扩展,只能通过修改代码来实现,缺乏其他灵活的途径。
总的来说,单例模式在特定场景下能够发挥其优势,有效地管理资源和控制访问,但在扩展性方面存在一定的局限性。在实际应用中,需要根据具体需求权衡其利弊,选择是否使用单例模式。