单例模式是一种常见的设计模式,用于确保一个类只有一个实例,并提供一个全局访问点。在Java中,有多种实现单例模式的方式,每种方式都有其优缺点,特别是在并发环境下。以下是几种常见的单例模式实现方式及其并发问题的解决方案:
1. 饿汉加载(Eager Initialization)
1 2 3 4 5 6 7 8 9
| public class Singleton { private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() { return INSTANCE; } }
|
优点:
缺点:
- 实例在类加载时创建,可能会浪费资源,尤其是当实例化成本较高且实际使用频率较低时。
并发问题:
2. 懒汉加载(Lazy Initialization)
1 2 3 4 5 6 7 8 9 10 11 12
| public class Singleton { private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
|
优点:
缺点:
- 不是线程安全的,多个线程同时调用getInstance()时可能会创建多个实例。
并发问题:
解决方案:
1 2 3 4 5 6 7 8 9 10 11 12
| public class Singleton { private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
|
- 双重检查锁定(Double-Check Locking):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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; } }
|
- 使用volatile关键字确保instance的可见性,避免指令重排序问题。
- 双重检查确保在多线程环境下只创建一个实例,同时减少锁的开销。
3. 静态内部类(Static Inner Class)
1 2 3 4 5 6 7 8 9 10 11
| public class Singleton { private Singleton() {}
private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); }
public static Singleton getInstance() { return SingletonHolder.INSTANCE; } }
|
优点:
- 实现简单。
- 线程安全,无需额外的同步机制。
- 实例在第一次使用时创建,节省资源。
缺点
并发问题:
4. 枚举(Enum)
1 2 3 4 5 6 7
| public enum Singleton { INSTANCE;
public void someMethod() { } }
|
优点:
- 实现简单。
- 线程安全,无需额外的同步机制。
- 防止反序列化重新创建新的对象。
缺点:
并发问题:
5. 反序列化和反射问题
- 反序列化: 如果单例类实现了Serializable接口,反序列化时可能会创建新的实例。
- 解决方案: 在单例类中添加readResolve方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class Singleton implements Serializable { private static final long serialVersionUID = 1L;
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() { return INSTANCE; }
protected Object readResolve() { return INSTANCE; } }
|
- 反射: 通过反射可以调用私有构造函数,创建新的实例。
- 解决方案: 在构造函数中添加检查逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class Singleton { private static final Singleton INSTANCE = new Singleton();
private Singleton() { if (INSTANCE != null) { throw new IllegalStateException("Already initialized"); } }
public static Singleton getInstance() { return INSTANCE; } }
|
总结
- 饿汉加载:简单且线程安全,但可能浪费资源。
- 懒汉加载:节省资源,但需要解决并发问题,推荐使用双重检查锁定。
- 静态内部类:简单、线程安全且节省资源。
- 枚举:简单、线程安全且防止反序列化问题,但使用场景有限。
根据实际需求选择合适的单例模式实现方式,并注意解决并发问题。