详解
单例模式是一种创建型设计模式,用于确保一个类只有一个实例,并提供全局访问点。
- 提供唯一的实例,避免产生多个实例造成资源浪费。
- 全局访问点使得其他对象可以方便地访问该实例。
饿汉式(Eager Initialization)
饿汉式是在类加载时就创建实例,并始终保持单例。
示例代码如下:
public class EagerSingleton {
// 在类加载时就创建实例
private static final EagerSingleton instance = new EagerSingleton();
// 构造函数私有化,避免通过new创建实例
private EagerSingleton() {
}
// 获取实例的方法
public static EagerSingleton getInstance() {
return instance;
}
}
懒汉式(Lazy Initialization)
懒汉式是在第一次使用时才创建实例,避免不必要的资源消耗。需要注意线程安全问题。
示例代码如下:
public class LazySingleton {
private static volatile LazySingleton instance;
private LazySingleton() {
}
public static LazySingleton getInstance() {
if (instance == null) {// 第一次检查,确保性能,如果已创建,不走同步块
synchronized (LazySingleton.class) {
if (instance == null) {//第二次检查,确保单例
instance = new LazySingleton();
}
}
}
return instance;
}
}
静态内部类(Static Inner Class)
静态内部类是一种延迟加载的方式,并且保证了线程安全性。
示例代码如下:
public class StaticInnerSingleton {
private StaticInnerSingleton() {
}
// 静态内部类,用于延迟加载
private static class SingletonHolder {
private static final StaticInnerSingleton instance = new StaticInnerSingleton();
}
public static StaticInnerSingleton getInstance() {
return SingletonHolder.instance;
}
}
枚举(Enum)
枚举方式是Java中最简洁、安全的单例模式实现方式,可以避免通过反射和反序列化破坏单例。
示例代码如下:
public enum EnumSingleton {
INSTANCE;
// 添加其他方法和属性
public void doSomething() {
// 执行操作
}
}
破坏单例模式
单例模式可以通过反射和反序列化来破坏。下面是详细解释:
- 反射破坏单例模式:通过使用反射,可以访问私有构造方法,并强制创建多个实例。
示例代码如下:
public class ReflectionSingletonTest {
public static void main(String[] args) {
EagerSingleton instance1 = EagerSingleton.getInstance();
EagerSingleton instance2 = null;
try {
Constructor<EagerSingleton> constructor = EagerSingleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
instance2 = constructor.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Instance 1 hashCode: " + instance1.hashCode());
System.out.println("Instance 2 hashCode: " + instance2.hashCode());
}
}
输出结果:
Instance 1 hashCode: 366712642
Instance 2 hashCode: 1829164700
如上所示,通过反射可以创建出多个实例,破坏了单例模式。
- 反序列化破坏单例模式:当一个单例类被序列化后,再次进行反序列化时,会创建新的实例。
示例代码如下:
public class SerializedSingletonTest {
public static void main(String[] args) {
SerializedSingleton instance1 = SerializedSingleton.getInstance();
try {
// 将实例进行序列化
FileOutputStream fileOut = new FileOutputStream("singleton.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(instance1);
out.close();
fileOut.close();
// 将序列化后的实例进行反序列化
FileInputStream fileIn = new FileInputStream("singleton.ser");
ObjectInputStream in = new ObjectInputStream(fileIn);
SerializedSingleton instance2 = (SerializedSingleton) in.readObject();
in.close();
fileIn.close();
System.out.println("Instance 1 hashCode: " + instance1.hashCode());
System.out.println("Instance 2 hashCode: " + instance2.hashCode());
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出结果:
Instance 1 hashCode: 1216424564
Instance 2 hashCode: 1208627083
如上所示,通过反序列化可以创建出多个实例,破坏了单例模式。
防止破坏单例模式
为了避免反射和反序列化对单例模式的破坏,可以采取以下措施:
-
私有化构造方法:将单例类的构造方法设置为私有,防止外部类通过new关键字创建新的实例。
-
防止反射破坏:可以在单例类的私有构造方法中添加逻辑,当检测到已存在实例时,抛出异常,防止通过反射创建新的实例。也可以在单例类中添加一个标志位,当已存在实例时,再次调用构造方法时直接返回已有实例。
-
防止反序列化破坏:可以在单例类中实现
readResolve()
方法,返回已有实例,防止通过反序列化创建新的实例。
下面是一个综合应用了上述措施的单例模式示例代码:
public class Singleton implements Serializable {
private static final long serialVersionUID = 1L;
private static volatile Singleton instance;
private Singleton() {
// 防止通过反射创建新的实例
if (instance != null) {
throw new RuntimeException("Cannot create new instance, please use getInstance() method.");
}
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
// 防止反序列化创建新的实例
protected Object readResolve() {
return getInstance();
}
}