[JAVA] Singleton Design Pattern 개념 및 구현
Singleton Design Pattern이란 클래스가 단 하나만의 인스턴스를 생성하도록 제한하는 패턴이다. private
및 static
제어자를 활용해 클래스 내부에서 미리 인스턴스 하나를 생성해두고, 외부에서 접근할 때는 해당 인스턴스에 대한 참조값만 전달한다.
보통 여러 개의 인스턴스가 필요 없는 경우에 사용한다. 예를 들자면,
- 수정 가능한 멤버변수가 없고 기능만 있는
stateless
객체의 경우 - 인스턴스를 계속 생성 및 삭제하는데 비용이 많이 들어서 재사용이 유리한 경우
구현
자바에서 Singleton Design Pattern을 여러 가지 방법으로 구현할 수 있다.
공통 구현 내역
- 생성자를
private
으로 선언해 내부에서만 인스턴스 생성할 수 있도록 제한 private Singleton() { }
- 클래스 내부에 인스턴스를 저장할 참조변수를
private static
으로 생성 private static Singleton instance;
- 해당 인스턴스에 대한 참조값을 리턴해주는
public static
getter 제공 public static Singleton getInstance() { return instance; }
기본 구현
클래스가 메모리에 로드될 때, 인스턴스를 생성하는 방법이다.
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() { }
public static Singleton getInstance() {
return s;
}
}
인스턴스를 사용할 때가 되지 않았는데, 클래스가 메모리에 로드될 때 이미 함께 인스턴스가 생성되어 메모리 공간을 차지하고 있는 문제가 있다.
Lazy Loading with null
인스턴스가 실제로 사용될 때, 메모리에 생성되도록 Getter에서
null
을 사용해 확인 후 생성하는 방식이다.
public class Singleton {
private static Singleton instance;
private Singleton() { }
public static Singleton getInstance() {
if (instance == null)
instance = new Singleton();
return instance;
}
}
사용할 때 인스턴스가 생성되는 장점이 있지만, thread unsafe하다는 단점이 있다. 멀티스레딩 환경에서 한 스레드가 인스턴스 생성을 완료하기 이전에 다른 스레드가 getInstance()
에 접근한다면 인스턴스가 중복 생성되는 문제가 발생할 수 있다.
Lazy Loading with synchronized
Getter에
synchronized
제어자를 사용해 thread safe하게 인스턴스를 생성한다.
Synchronized Method
메서드 전체에 synchronized 선언하는 방식이다.
public static synchronized Singleton getInstance() {
if (instance == null)
instance = new Singleton();
return instance;
}
임계영역이 메서드 전체로 잡혀서 오버헤드 증가하는 문제가 있다
Synchronized Block (double-checked locking)
synchronized (this), synchronized (A.class)와 같은 형식으로 선언하는 방식으로, 해당하는 변수나 클래스에만 synchronized 선언한다.
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null)
instance = new Singleton();
}
}
return instance;
}
메서드 전체보다는 해당하는 변수나 클래스에만 synchronized
를 선언하는 것이 바람직하다. 하지만 아무리 synchronized block으로 영역을 제한한다고 해도, 싱글 스레드 상황에서는 synchronized
를 쓰지 않는 것이 제일 효율적이다.
Lazy Loading with initialization-on-demand holder
추가적인 클래스를 사용해 thread safe하면서도 효율적인 방식으로 인스턴스를 생성하는 방식이다.
public class Singleton {
private Singleton() { }
private static class LazySingletonHolder {
static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return LazySingletonHolder.INSTANCE;
}
}
Singleton
클래스가 메모리에 로드될 때, 아무런 인스턴스도 생성되지 않는다. Singleton
의 getInstance
함수가 불릴 때에야 JVM이 LazySingletonHolder
클래스를 메모리에 로드하고 초기화하며 인스턴스를 생성한다. 클래스를 메모리에 로드하고 초기화하는 과정은 순차적으로 발생하도록 보장되어 있기 때문에 추가적인 thread safety를 보장하기 위한 추가 조치가 필요하지 않다.
원래 inner class에서 top level class의 private
생성자나 멤버변수, 메서드에 접근하려면 클래스가 static
이 아니어야 하지만, class LazySingletonHolder
의 경우, 멤버변수가 static
이라 괜찮다.
테스트
public class SingletonTest {
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2); // true
}
}
기타
Spring도 Singleton Design Pattern을 사용한다. 기본적으로 Spring 컨테이너가 객체를 생성할 때, 하나의 객체만 생성되도록 한다.