Singleton Pattern

# Singleton Pattern

싱글톤 패턴(Singleton pattern)은 클래스의 객체화를 한번으로 제한함으로써 프로그램 전반에 걸쳐서 같은 객체에 접근하도록 하는 패턴 입니다.
자주 접하는 대표적인 예로는 logger 가 있겠네요.
아래는 log4jslf4j(logback) 사용 예 입니다. new()를 이용해서 객체를 생성하지 않고 정적 팩토리 메소드를 이용해서 객체를 생성하는것을 확인 할 수 있습니다.

1
2
3
4
5
//log4j
private final Logger logger = Logger.getLogger(Example.class);

//slf4j(logback)
private final Logger logger= LoggerFactory.getLogger(Example.class);

싱글톤 패턴을 구현하는 방법은 여러가지가 있는데 그중에서도 효과적인 방법 네가지만 알아보도록 하겠습니다.

1. Eager initialization

가장 기본적인 싱글톤 패턴의 구현방법으로 싱글스레드 프로그래밍에서 간단하게 사용할때 사용가능합니다.
**핵심은 **
- 생성자를 private으로 만들어서 외부에서 new()로 생성불가능하게 하고
- 클래스 내에 private static final로 자기 자신을 instance화 해서 미리 만들어 놓습니다.
- 외부에서는 public static으로 공개된 getInstance() 메소드를 통해서 미리 만들어 놓은 instance를 받아서 쓰게 됩니다.

1
2
3
4
5
6
7
8
9
10
11
public class EagerInitializedSingleton {

private static final EagerInitializedSingleton instance = new EagerInitializedSingleton();

//private constructor to avoid client applications to use constructor
private EagerInitializedSingleton(){}

public static EagerInitializedSingleton getInstance(){
return instance;
}
}

2. Lazy Initialization & thread safe way of Lazy Initialization

앞에서 살펴본 Eager initialization 방식의 단점중에 한가지는 static 변수로 객체를 미리 다 할당해 놓기 때문에 아무도 해당 객체를 호출하지 않더라도 불필요하게 메모리에 올라가게 됩니다. 어짜피 요즘환경에서 실무에서는 못쓰는 방식이라 신경쓰일 만큼의 대용량 객체가 생성되진 않겠지만 예전 싱글쓰레드에서 메모리에 민감한 사람들이 아래 Lazy Initialization idiom을 생각해 내지 않았나 하고 개인적으로 생각 됩니다 ㅎㅎ
핵심은
- 여전히 private으로 생성자를 만들어놔서 외부에서 new()로 객체를 생성 하는것은 제한합니다.
- 이번에는 private static으로 선언한 클래스자신의 변수인 instance 를 바로 new() 로 생성해 놓지 않습니다.
- public static으로 외부에서 호출하는 getInstance()메서드 내부에서 현재 instance 객체가 null이면 new()를 하고 그게 아니면 만들어진 instance를 돌려줍니다.
즉, 미리 싱글톤 객체를 생성해 놓는것이 아니라 누군가 최초에 한번 호출을 하면 그제서야 객체를 생성 하는것 입니다.
하지만 null 체크하는부분에서 ** 여러 쓰레드가 동시에 접근해 버리면 여러개의 객체가 생성되는 문제 ** 가 있고 이를 해결하려면 간단히 getInstance() 메서드에 synchronized를 붙여서 동기화시키면 쓰레드 쎄잎 한 싱글톤 클래스를 생성할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
public class LazyInitializedSingleton {

private static LazyInitializedSingleton instance;

private LazyInitializedSingleton(){}

public static LazyInitializedSingleton getInstance(){
if(instance == null){ //이부분이 Thread safe 하지 않게되는 원인 입니다.
instance = new LazyInitializedSingleton();
}
return instance;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class ThreadSafeSingleton {

private static ThreadSafeSingleton instance;

private ThreadSafeSingleton(){}

public static synchronized ThreadSafeSingleton getInstance(){
if(instance == null){
instance = new ThreadSafeSingleton();
}
return instance;
}
}

참고로 getInstance() 메소드 전체를 동기화 시킴으로써 성능적으로 손해보는걸 조금이라도 줄여보겠다!
라고 한다면 아래와같이 null일때만 동기화 시키는 방법도 있습니다. (double-checked locking idiom)

3. Enum Singleton

이 방법은 이펙티브자바의 저자인 조슈아 블로크 형님께서 java1.5 이상에서 싱글톤을 구현하는 가장 좋은 방법이라고 소개한 방식입니다.

간결하면서도 직렬화가 자동으로 처리되고 리플렉션공격에도 안전 합니다.

1
2
3
4
5
6
7
8
public enum EnumSingleton {

INSTANCE;

public static void doSomething(){
//do something
}
}

음… 이걸 어떻게 쓰라는거지………..??!! 사실 이번 포스트를 쓰게 된 계기이기도 합니다 ㅎㅎ 저도 매번 헷갈려서..
어렵게 생각할것 없이 그냥 아래와 같이 일반 클래스 생성하듯이 생성하고 호출하면 됩니다.

1
2
EnumSingleton enumSingleton = EnumSingleton.INSTANCE;
enumSingleton.doSomthing();
4. Bill Pugh Singleton

마지막으로 Bill Pugh 라는 분이 만들어낸 싱글턴 패턴의 구현 입니다.
JVM이 클래스를 로드하는 방식&순서를 이용해서 싱글톤을 구현한 idiom 이며 아래 JLS(Java Language Spec)에 따라
https://docs.oracle.com/javase/specs/jls/se8/html/jls-12.html#jls-12.4.1
https://docs.oracle.com/javase/specs/jls/se8/html/jls-12.html#jls-12.4.2
https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.1.3
inner클래스는 outter클래스가 로드될때 같이 로드되지 않고,
클래스가 객체화 될때는 fully initialized 되기 전까지는 사용할 수 없다는 점을 이용했다고 합니다.

즉, inner클래스(holder)가 outter클래스(실제 싱글톤으로 사용하고 싶은 클래스)의 객체를 완전히 생성하기 전까지는 다른 쓰레드에서는 해당 클래스를 접근할 수 없고 final로 선언되어있으므로 이후 접근시에는 같은 객체를 계속 반납해 주는 방식 입니다.
다른 lazy initialization 방식들과 눈에 띄게 다른점은

  1. static 변수를 가지고 있지 않다는 점 (다른패턴에서는 static변수를 미리 만들어 놓고 나중에 사용할 때 객체생성 했었죠)
  2. inner class에 private static final 변수를 가지고 있음.

가장 널리 이용되는 싱글톤패턴의 구현방법이며 가장 빠르게 동작하는 싱글톤 패턴의 구현 방식으로도 알려져 있습니다.
하지만 직렬화를 위해 추가로 작성해 줘야 할 부분이 있고, 리플렉션공격에 취약한 단점이 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class SingletonBillPugh {

/* private constructor */
private SingletonBillPugh() {
}

/* static inner class */
private static class LazyHolder{
private static final SingletonBillPugh INSTANCE = new SingletonBillPugh();
}

/* get instance of SingletonBillPugh */
public static SingletonBillPugh getInstance(){
return LazyHolder.INSTANCE;
}
}

지금까지 유명한 싱글톤 패턴의 이디엄들을 살펴 봤는데요
누군가에게는 도움이 되셨으면 좋겠습니다.

참조 자료
https://www.journaldev.com/1377/java-singleton-design-pattern-best-practices-examples
https://dzone.com/articles/java-singletons-using-enum
https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom
http://www.javaquery.com/2016/07/bill-pugh-initialization-on-demand.html

Share