싱글턴(singletone)이란 인스턴스를 오직 하나만 생성할 수 있는 클래스를 말한다.[Gamma95]
Motivation
클래스별로 유일한 인스턴스를 보장하는 것이 중요한 경우가 있다. 싱글톤은 리소스의 관리를 중앙화해서 access point를 딱 하나만 제공하는 역할을 한다.
싱글톤 패턴은 가장 간단한 디자인 패턴중 하나이다.
퍼블릭 생성자를 막자. 그리고 딱하나만 만들고 그것을 주면된다.
Intent
- 인스턴스를 딱하나만 만들기
- global access point를 딱하나만 제공하기
Implementation
싱글톤을 구현하는 가장 간단한 방법
1. 생성자를 private으로 감추기 2. public static 멤버를 통해 딱하나만 생성한 인스턴스 제공하기
// Thread Unsafe, Lazy Initilization class Singleton { private static Singleton instance; private Singleton() { ... } public static Singleton getInstance(){ if (instance == null) instance = new Singleton(); return instance; } ... public void doSomething() { ... }
Examples
- 로거 클래스
- 설정 클래스
- 팩토리 클래스
Specific Issues
Thread-safe implementation
- Early instantiation using implementation with static field
//Early instantiation using implementation with static field. class Singleton { private static final Singleton instance = new Singleton(); private Singleton(){} public static Singleton getInstance(){ return instance; } }
- Lazy instantiation using double chekced locking mechanism.
//Lazy instantiation using double chekced locking mechanism. class Singleton { private static volatile Singleton instance; //자바 1.4이하의 메모리 관리 기술도 이해해야함 private Singleton(){ } public static Singleton getInstance(){ if (instance == null) { synchronized(Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
Protected constructor
간단히 싱글톤으로 만들려면 private 생성자를 쓰면된다.
근데 private 생성자만 쓰면 상속을 할 수 없다. 그렇다고 public을 쓰면 싱글톤을 보장 할 수 없다.
그래서 절충안으로 protected 생성자를 쓰게되면 한가지 단점이 있다.
- 같은 패키지에서 생성자 호출 가능 → 싱글톤 만을위한 패키지를 만들어 해결 가능
Serialization
싱글턴 클래스를 직렬화하려면 Serializable을 구현한다고 선언하는 것만으로는 부족하다.
- 모든 인스턴스 필드를 일시적 (transient)라고 선언하고
- readResolve 메서드를 제공해야 한다.
이렇게 하지 않으면 직렬화된 인스턴스를 역직렬화할 때마다 새로운 인스턴스가 생성된다.
Reflection 공격
- 권한이 있는 사용자는
AccessibleObject.
setAccesible
을 사용해 private 생성자를 호출할 수 있음
- 생성자를 수정해서 두번 째 객체가 생성되려 할 때 예외를 던지게 하여 방어 할 수 있음
Stateless
- 특정 클라이언트에 의존적인 필드가 있으면 안된다.
- 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안된다!
- 가급적 읽기만 가능해야 한다.
- 필드 대신에 자바에서 공유되지 않는, 지역변수, 파라미터, ThreadLocal 등을 사용해야 한다.
인스턴스 제공 멤버로 필드 vs 메서드
필드
- 명확하다.
- 간결하다.
메서드
- API의 변경없이 생성 정책을 변경할 수 있다.
- e.g.) 완전 싱글턴 → 쓰레드별로 인스턴스 제공 (see also - ThreadLocal)
- static factory method → generic singletone factory로 변경가능
- Method Reference 활용 가능
- e.g.)
Elvis::getinstance
를Supplier<Elvis>
로 활용
세 번째 방법은 원소가 하나인 열거 타입을 선언하는 것
public enum Elvis { INSTANCE; }
- 더 간결함
- 직렬화, 리플렉션 관련 이슈 없음
- 대상 싱글턴 객체가 Enum 외의 클래스를 상속해야하는 경우 사용할 수 없음
- 대부분의 상황에서는 가장 좋은 방법이다.
네 번째 방법은 static inner 클래스 사용하기
(Bill Pugh Solution)
//inner static class 활용 class Singleton { private Singleton() {} private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.Instance; } }
Reference)
이펙티브 자바 Effective Java 3/E
- 아이템 3. private 생성자나 열거 타입으로 싱글턴임을 보증하라
백기선 - [GoF 디자인 패턴]
See Also)
싱글톤 레지스트리 (e.g.스프링 컨테이너,
@Configuration
@Bean
→ DI, IOC)