///
Search
💡

Item 3. private 생성자나 열거 타입으로 싱글턴임을 보증하라

싱글턴 패턴이란?

인스턴스를 하나만 생성할 수 있는 패턴을 의미한다. 싱글턴으로 만들 수 있는 객체는 함수와 같은 무상태 객체나 설계상 유일해야 하는 시스템 컴포넌트를 예로 들 수 있다.

싱글턴 패턴 구현하기

3가지 방식이 있다.
1.
public static으로 인스턴스를 생성하는 방식
public class Elvis { public static final Elvis INSTANCE = new Elvis(); private Elvis() { ... } public void leaveTheBuilding() { ... } }
Java
복사
이 방식의 장점은 해당 클래스가 싱글턴임이 API에 명백히 드러난다는 것이다. final이기 때문에 다른 객체를 참조할 수 도 없다. 두 번째 장점은 코드 작성의 간결함이다.
2.
정적 팩토리 메서드를 활용하는 방식
public class Elvis { private static final Elvis INSTANCE = new Elvis(); private Elvis() { ... } public static Elvis getInstance() { return INSTANCE; } public void leaveTheBuilding() { ... } }
Java
복사
해당 방식이 1번 보다 나은 장점은 싱글턴이 아니라 다른 방식으로 변환하고 싶을 때 자유롭게 변경할 수 있다는 장점이 있다. 두 번째 장점은 정적 팩토리를 제네릭 싱글턴 팩토리로 만들 수 있다(아이템 30)는 점이고, 세 번째 장점은 해당 메소드를 Supplier<Elvis> 에 메소드 레퍼런스로 넘겨줄 수 있다는 것이다.
위 두 가지 방식으로 만든 싱글턴 객체를 직렬화 하려면 Serializable을 implement하는 것으론 모자라다. 모든 인스턴스 필드를 transient(직렬화 할 때 제외)로 선언하고 readResolve 메서드를 제공해야 한다. 이렇게 하지 않으면 역직렬화를 할 때 마다 새로운 인스턴스가 생겨나기 때문이다.
이를 방지하려면 다음과 같이 readResolve 메소드를 구현해야 한다.
private Object readResolve() { // '진짜' Elvis를 반환하고 오버라이드 한 역직렬화 과정에서 생겨난 Elvis는 Garbage Collector에 맡긴다 return INSTANCE; }
Java
복사
3.
원소가 하나인 열거 타입을 선언하기
public enum Elvis { INSTANCE; public void leaveTheBuliding() { ... } }
Java
복사
1번 방식과 비슷하지만 더 단순하고 더 간결하고 추가 노력없이 직렬화할 수 있는 방법이다. 어색해 보일 수 있으나 대부분 상황에서 원소가 하나 뿐인 enum이 싱글턴을 만드는 가장 좋은 방법이다.

부록 1.

예제에선 싱글톤 방식에 대해 간단하게 소개되었지만, 싱글톤 생성 방식도 최적화하는 여러 방식이 존재한다.
1.
Eager Initialization
클래스 로딩시점에 생성하는 방식이다. 만약 instantce가 큰 리소스를 점유하는 객체라면, 사용하지 않을 객체를 미리 생성하게 되어 메모리 낭비가 발생할 수 있다.
public class EagerInit { private static EagerInit instance = new EagerInit(); private EagerInit() { System.out.println("Constructor"); } public static EagerInit getInstance() { return instance; } public void print() { System.out.println("EagerInit instance Hash : " + instance.hashCode()); } }
Java
복사
2.
Lazy Initialization
객체 사용시점에 생성하는 방식이다. 그러나 getInstance에 두 개 이상의 스레드가 동시 접근 하였을 때 2개 이상의 객체가 2개 생길 수 있다는 문제점이 있어, synchronized 블록으로 lock을 거는 방식이다.
public class SyncSingleton { private static SyncSingleton instance; private SyncSingleton() { System.out.println("Constructor"); } public static SyncSingleton getInstance() { if(instance==null) { synchronized (SyncSingleton.class) { if(instance==null) { instance = new SyncSingleton(); } } } return instance; } public void print() { System.out.println("SyncSingleton instance Hash : " + instance.hashCode()); } }
Java
복사
3.
Lazy Holder
Lazy Initialization에서 thread safe하려면 코드가 꼴보기 싫어진다. 원리 상 큰 차이는 없지만 thread safe하게 가져가는 트릭으로 Lazy Holder가 있다. inner static class는 Singleton 클래스가 로드되는 시점이 아닌 getInstance()가 호출되는 시점에 로드되는데, 클래스 로딩 시점엔 lock을 사용하므로 Thread-safe가 보장된다.
public class Singleton { private Singleton() { System.out.println("Constructor"); } private static class LazyHolder { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return LazyHolder.INSTANCE; } public void print() { System.out.println("Singleton instance Hash : " + LazyHolder.INSTANCE.hashCode()); } }
Java
복사

부록 2. Singleton Scope

용어사전

한글명
영어명
싱글턴 패턴
singleton pattern
무상태
stateless