编程Java后端开发工程师

如何在Java中实现考虑多线程和序列化的单例模式?实现时有什么注意事项?

用 Hintsage AI 助手通过面试

答案

经典的单例模式保证只创建一个对象实例。在Java中有几种实现方式,但考虑到多线程和序列化,需要注意以下细节:

  1. 线程安全实现(双检锁): 常用于懒加载。
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; } }
  1. 枚举单例: 从防止序列化、反射和多线程的角度来看是最佳方式。
enum EnumSingleton { INSTANCE; // 方法 }
  1. 序列化问题: 普通单例在序列化/反序列化后可能会失去实例的唯一性。为了正常工作,需要添加 readResolve() 方法:
private Object readResolve() { return getInstance(); }

带陷阱的问题

仅使用 synchronized 方法 getInstance() 是否足以实现线程安全的单例?

答案: 是的,但这种方法会导致性能下降,因为每次调用 getInstance 时都会调用 synchronized。使用双检锁 + volatile 来处理 instance,或者使用枚举来实现单例会更有效。

低效代码示例:

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

历史

在金融系统中实现单例时,忘记在双检锁中为 instance 引用添加 volatile。结果在高负载下随机出现创建两个类实例的情况,导致报告不一致。


历史

在日志库中通过私有静态对象实现了单例,但在序列化和后续反序列化(例如,在集群环境中)时出现了多个实例。通过添加 readResolve() 解决了问题。


历史

在分析系统中,开发人员通过 synchronized 方法 getInstance() 实现了线程安全的单例。在高负载系统的高峰时,性能突然下降,发现:由于不必要的同步,getInstance() 的调用(每秒数千次)互相阻塞,尽管初始化只需进行一次。