다양한 패턴들 중 처음으로 작성할 포스팅은 싱글톤 패턴에 관해서이다. 스프링을 주제로 삼고 있지만, 객체지양언어에서 아키텍처와 패턴은 빼놓을 수 없기에 고민 끝에 여기에 작성해보고자 한다.
싱글톤 패턴은 가장 유명한 패턴들 중 하나로, 디자인 패턴을 따로 공부하지 않은 사람들도 알고 있는 경우가 많다. 하지만 유명한 만큼 예제에 쉽게 작성할 수 있어서 어설프게나마 따라하지만, 정작 왜 써야하는지에 대한 이해를 하는 경우가 많지 않다. 나도 그렇다. 따라서 이번 기회에 한번 자바로 한번 예제를 써보고 기록으로 남기고자 한다.
inheritnance
보다 composite
을 사용하는 방향으로 발전여기서 생성패턴의 특징은 아래와 같다.
클래스의 인스턴스가 오직 하나임을 보장하고, 이 인스턴스에 접근할 수 있는 전역적인 접촉점을 제공하는 패턴이다. 프로그램 시작단계부터 종료시까지 인스턴스는 메모리상에 단 1개만 존재하는 것을 목절으로 한다.
개발을 하다보면 어떤 클래스에 대한 단 하나의 인스턴스만 필요한 경우가 있다. 예를들면.. 로그를 찍는 객체라던지, 대기큐라던지, 쓰레드 풀이라던지, 윈도우 관리자라던지 등등 어플리케이션에서 하나의 인스턴스를 갖는 것이 바람직한 경우가 꼭 있다.
가장 먼저 떠오르는 것은 전역변수를 사용하는 것이다. 물론 맞는 방법일 수 있겠으나, 가장 좋은 방법은 클래스 자신이 자기의 유일한 인스턴스로 접근하게 만드는 것이다!! (만일 협업 중에서 기존 소스코드에 미숙한 사람이 하나의 인스턴스를 또 추가해버린다고 생각해보자!)
다시 말하자면, 생성자를 private 으로 선언해서 인스턴스화 할 수 없게 만듬과 동시에 내부에서 단 하나의 인스턴스 getter 를 만들어 주는것이 가장 대표적인 사용 방법이다.
instance value
로 하나의 인스턴스를 추가하자예제 소스는 아래와 같다.
public class Singleton {
private static final Singleton instance = new Singleton();
// private constructor to avoid client applications to use constructor
private Singleton(){}
public static Singleton getInstance(){
return this.instance;
}
}
이 방법의 경우 싱글톤 클래스가 다소 적은 리소스를 다룰때 사용하는 것이 좋다.
File System, Database Connection 과 같은 큰 리소스를 다루는 싱글톤을 구현할때는 위와 같은 방식보다는 GetInstance() 메소드가 호출되기 전까지 생성하지 않는 것이 좋다.
public class Singleton {
private static Singleton instance;
private Singleton(){}
//static block initialization for exception handling
static{
try{
instance = new Singleton();
}catch(Exception e){
throw new RuntimeException("Exception occured in creating singleton instance");
}
}
public static Singleton getInstance(){
return instance;
}
}
이 경우 차이점은 예외처리를 한다는 점이다. 하지만 클래스 로딩 단계에서 인스턴스를 생성한다는 건 Eager Initialization
과 다른점이 없다.
public class Singleton {
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
프로그램 시작단계에서 바로 인스턴스를 생성하지 않는 다는 측면에서 이름이 쉽게 이해갈 것이다. 사용하지 않을 경우 메모리 낭비가 방지된다는 장점이 있다. 하지만 multi-thread 환경에서 동기화가 될 수 있다는 것을 잊어서는 안된다. 여러 스레드에서 만일 동시에 생성한다면 싱글톤 패턴에 문제가 생길 수 있다.
public class Singleton {
private static Singleton instance;
private Singleton(){}
public static synchronized Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
Thread Safe Singleton 은 3번의 문제를 해결할 수 있는 방식으로 synchronized 제어자를 메소드 앞에 추가해놓은 것이 특징이다. 위와 같은 방식으로 구현한다면 getInstance() 메소드 내에 진입하는 쓰레드가 하나로 보장받기 때문에 멀티쓰레드 환경에서도 정상작동할 것이다. 하지만 syncronized 키워드 자체에 대한 비용이 크다고 한다. 인스턴스 호출이 잦은 어플리케이션에서는 성능이 저하 될 수 있다.
public static Singleton getInstance(){
if(instance == null){
synchronized (Singleton.class) {
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
4번의 문제를 해결하기 위해 고안된 방식이다. getInstance() 메소드에서 임계영역을 걸지 않고, instance가 null인 경우에만 lock 을 거는 것이다.
public class Singleton {
private Singleton(){}
private static class SingletonHelper{
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance(){
return SingletonHelper.INSTANCE;
}
}
inner class의 특징을 알고 있는 경우 이해하기 쉽다. private inner static class 를 두어 싱글톤 인스턴스를 갖게 한다. 이때 1, 2번과의 차이점은 SingletonHelper 클래스는 메모리에 적재 당시에 바로 Load 되는 것이 아니고, getInstance() 메서드가 호출했을 때, JVM 메모리에 로드되고, 인스턴스를 생성하게 된다.
앞선 1 ~ 5번의 해결방식은 완전히 안전하지 않을 수 있다. JAVA의 Reflection 을 통해서 싱글톤을 파괴할 수 있기 때문이다. 따라서 Joshua Bloch 는 Enum 으로 싱글톤을 구현하는 방법을 제안했다.
여기서 enum 클래스는 상수들만 모아놓은 클래스라고 할 수 있다. 때문에 클래스 처럼 메소드 생성자를 가질 수 있기도 하다.
Enum 은 private 생성자로 인스턴스 생성을 제어하고, 상수만 갖는 특별한 클래스이기 때문에 싱글톤의 성질을 일반적으로 갖고 있다.
또한 Enum Singleton은 Thread-safety 와 Serialization 이 보장되고, Reflection 에도 안전하다.
public enum Singleton {
INSTANCE;
private Object readResolve() {
return INSTANCE;
}
}