전통적인 싱글톤은 객체의 인스턴스가 하나만 생성되도록 보장합니다. Java에는 여러 가지 구현 방법이 있지만, 스레드 안전성과 직렬화를 고려할 때는 다음과 같은 유의사항이 있습니다:
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; } }
enum EnumSingleton { INSTANCE; // 메소드들 }
private Object readResolve() { return getInstance(); }
스레드 안전 싱글톤을 위해 synchronized 메소드 getInstance()를 사용하는 것으로 충분한가요?
답변: 네, 하지만 이러한 접근 방식은 성능 저하를 초래합니다. 왜냐하면 synchronized는 getInstance에 접근할 때마다 호출되기 때문입니다. 더 효율적인 방법은 Double-checked Locking + volatile을 instance에 사용하는 것이거나, 싱글톤 구현을 위해 Enum을 사용하는 것입니다.
비효율적인 코드 예시:
public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; }
역사
금융 시스템에서 싱글톤 구현 시 double-checked locking의 instance 참조에
volatile을 추가하는 것을 잊어버렸습니다. 그 결과 고부하에서 클래스의 두 인스턴스가 랜덤하게 생성되는 경우가 발생하여 보고의 일관성이 무너지게 되었습니다.
역사
로깅 라이브러리에서 싱글톤을 비공식 정적 객체를 통해 구현하였으나, 직렬화 및 이후 역직렬화(예: 클러스터 환경) 과정에서 여러 인스턴스가 발생했습니다. 문제는 readResolve()를 추가하여 해결되었습니다.
역사
분석 시스템에서 개발자가 synchronized 메소드 getInstance()를 통해 스레드 안전 싱글톤을 구현하였습니다. 고부하 시스템에서 피크 시간에 성능이 급격히 저하되었고, getInstance() 호출(초당 수천 번)이 불필요한 동기화로 서로를 차단하고 있음을 발견했습니다. 초기화는 한 번만 필요했기 때문입니다.