목차
싱글턴 패턴: 하나의 인스턴스에 접근하기
싱글턴은 한 클래스의 인스턴스 생성 경로를 통제하고, 공유 인스턴스에 접근하는 방법을 제공한다. 예를 들어 프로세스 전체에서 같은 설정값을 읽는 객체에 사용할 수 있다. 다만 여러 곳에서 사용한다는 이유만으로 싱글턴이 필요한 것은 아니다. 동일한 객체를 생성자 주입으로 공유해도 되는 경우가 많다.
Java에서 만드는 방법
public final class AppSettings {
private final String region = "ap-northeast-2";
private AppSettings() { }
private static class Holder {
private static final AppSettings INSTANCE = new AppSettings();
}
public static AppSettings getInstance() {
return Holder.INSTANCE;
}
public String region() {
return region;
}
}
생성자를 private로 두어 호출자가 new AppSettings()를 실행하지 못하게 한다. getInstance()는 Holder.INSTANCE를 반환한다. Holder 클래스가 초기화될 때 인스턴스가 만들어지므로 첫 호출 전에는 생성하지 않는다. Java의 클래스 초기화 과정이 동시 접근을 처리하므로 별도 synchronized가 필요하지 않다. 두 번 호출해 얻은 참조를 ==로 비교하면 같은 인스턴스다.
이 보장은 클래스 로더 하나 안에서 성립한다. 다른 프로세스, 다른 서버, 다른 클래스 로더까지 하나라는 뜻은 아니다. 따라서 전역 카운터나 잠금처럼 여러 서버에서 값을 공유해야 하는 문제는 싱글턴으로 해결할 수 없다. 또한 리플렉션이나 직렬화까지 엄격하게 제어해야 한다면 Java의 enum 싱글턴을 고려할 수 있다.
언제 피해야 할까?
가변 상태를 가진 싱글턴은 테스트 간 상태를 오염시키고, 요청들이 동시에 값을 바꿀 때 경쟁 조건을 만든다. 예를 들어 currentUser를 싱글턴 필드에 보관하면 서로 다른 사용자의 요청이 섞일 수 있다. 싱글턴을 호출하는 코드도 전역 의존성을 숨기므로 테스트에서 대체하기 어렵다. 설정이나 정책 객체를 여러 곳이 공유해야 한다면 우선 애플리케이션 조립 시 한 인스턴스를 만들고 필요한 객체에 생성자로 전달하는 방법을 검토한다.