프로그래밍Java 백엔드 개발자

Java에서 스레드 안전성과 직렬화에 대한 고려를 통해 싱글톤 패턴을 구현하는 방법은 무엇인가요? 구현 시 유의해야 할 사항은 무엇인가요?

Hintsage AI 어시스턴트로 면접 통과

답변

전통적인 싱글톤은 객체의 인스턴스가 하나만 생성되도록 보장합니다. Java에는 여러 가지 구현 방법이 있지만, 스레드 안전성과 직렬화를 고려할 때는 다음과 같은 유의사항이 있습니다:

  1. 스레드 안전한 구현 (Double-checked Locking): 지연 초기화에 자주 사용됩니다.
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 싱글톤: 직렬화, 리플렉션 및 스레드 안전성 측면에서 가장 좋은 방법입니다.
enum EnumSingleton { INSTANCE; // 메소드들 }
  1. 직렬화 문제: 일반적인 싱글톤은 직렬화/역직렬화 후 인스턴스의 유일성을 잃을 수 있습니다. 올바르게 작동하기 위해서는 readResolve() 메소드를 추가해야 합니다:
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() 호출(초당 수천 번)이 불필요한 동기화로 서로를 차단하고 있음을 발견했습니다. 초기화는 한 번만 필요했기 때문입니다.