[디자인 패턴] 싱글톤 패턴(Singleton Pattern - Java)

류명운

·

2016. 12. 26. 19:09

반응형

[디자인 패턴] 싱글톤 패턴(Singleton Pattern - Java)


싱글톤 패턴(Singleton Pattern) 이란?

소프트웨어 디자인 패턴에서 싱글턴 패턴(Singleton pattern)을 따르는 클래스는, 생성자가 여러 차례 호출되더라도 실제로 생성되는 객체는 하나이고 최초 생성 이후에 호출된 생성자는 최초의 생성자가 생성한 객체를 리턴한다. 이와 같은 디자인 유형을 싱글턴 패턴이라고 한다. 주로 공통된 객체를 여러개 생성해서 사용하는 DBCP(DataBase Connection Pool)와 같은 상황에서 많이 사용된다. - 위키백과 -

객체 지향 프로그래밍(OOP)의 개념이 생기면서 객체 자체에 대한 많은 연구와 패턴들이 생겨났다. 그 중 Singleton Pattern은 4대 디자인 패턴에 들어갈 정도로 흔히 쓰이는 패턴이다. Singleton Pattern은 객체의 인스턴스가 사용될 때에 똑같은 인스턴스를 만들어 내는 것이 아니라, 전 영역에 걸쳐 동일 인스턴스를 사용하게끔 하는 것을 뜻한다. 프로그래밍 상에서 동일한 커넥션 객체를 만든다던지, 하나만 사용되어야하는 객체를 만들때 매우 유용하게 사용한다.

또한, Singleton Pattern 이라는 하나의 디자인 패턴으로 불리지만 다양한 형태로 사용된다. 본 포스팅에서는 이러한 Singleton Pattern의 여러 구현 방법과 각 방법에 따른 장/단점을 알아보는 시간을 갖도록 하겠다. 본 포스팅에서 다루는 각 구현 방법은 다음에 한정한다.

  • basic initialization
  • static block initialization
  • lazy initialization
  • thread safe initialization
  • initialization on demand holder idiom


기본 초기화(basic initialization)

아래는 가장 기본적인 singleton pattern이다. 3번째 라인을 보면 전역 변수로 instance를 만드는데 private static을 이용한다. static이 붙은 클래스 변수는 인스턴스화에 상관없이 사용이 가능하게 된다. 하지만 앞의 private 접근제어자로 인해 BasicInitialization.instance로의 접근은 불가능하다. 이런 상태에서 생성자를 private로 명시한다. 

* 생성자를 private로 붙이게되면, new 키워드를 사용할 수 없게 된다.

즉, 다른 클래스에서 BasicInitialization instance = new BasicInitialization(); 이런 방법을 통한 인스턴스 생성이 불가능해진다. 결국 외부 클래스가 BasicInitialization 클래스의 인스턴스를 가질 수 있는 방법은 9번째 라인에 있는 getInstance() method를 사용하는 수 밖에 없다.

위의 단순한 singleton pattern은 리소스가 작은 프로그램일때엔 고도화 대상이 아니다. 하지만 프로그램의 크기가 커져서 수 많은 클래스에서 위와 같은 singleton pattern을 사용한다고 가정해보자. 3번째 라인의 new BasicInitialization(); 으로 인해 클래스가 load 되는 시점에 인스턴스를 생성시키는 작업이 부담스러울 수가 있다. 또한 위 소스는 BasicInitialization 클래스가 인스턴스화 되는 시점에 어떠한 에러처리도 할 수가 없다.

클래스가 로드될 때 최초 한번 초기화(static block initialization)

static 초기화블럭을 이용하면 클래스가 로딩될 때 최초 한번 실행하게 된다. 특히나 초기화블럭을 이용하면 logic을 담을 수 잇기 때문에 복잡한 초기변수 셋팅이나 위와 같이 에러처리를 위한 구문을 담을 수 있다.

위 기본 초기화 패턴(basic initialization pattern) 보다 좋아보이지만, 인스턴스가 사용되는 시점에 생성되는 것은 아니다.

인스턴스가 필요한 시점에 초기화(lazy initialization)

이제 클래스 인스턴스가 사용되는 시점에 인스턴스를 만드는 singleton parttern을 알아보도록 하자. 아래 소스의 lazy initialization pattern은 필요할때 인스턴스를 생성시키는 것이 핵심이다.

new LazyInitialization(); 가 어디에 선언되었는지 주목해보자. 5번째 라인의 getInstance() method 안에서 사용(7번째 라인)되었다. if문을 이용해 instance가 null 인지 확인하고 null 인 경우에만 new 를 사용해 객체를 생성하는 것을 확인할 수 있다. 최초 사용시점에만 인스턴스화 시키기 때문에 프로그램이 메모리에 적재되는 시점에 부담이 많이 줄게된다.

하지만 여전히 문제는 남아있다. 만약 프로그램이 multi thread 방식이라면 위와 같은 singleton pattern은 안전하지 않다. 동일 시점에 getInstance() method를 호출하게되면 인스턴스가 두번 생길 위험이 있다.

스레드에 안전한 초기화(thread safe initalization)

위에서 문제가 되었던 multi thread 문제를 해결하기 위해 5번째 라인을 보면 동기화(synchronized)를 사용하여 singleton pattern을 구현한 것을 확인 할수 있다. 여러 thread 들이 동시에 접근해서 인스턴스를 생성시키는 위험은 없어졌다.

하지만 이 또한 여전히 문제는 남아있다. 수 많은 thread 들이 getInstance() method를 호출하게 되면 높은 cost 비용으로 인해 프로그램 전반에 성능저하가 일어나게 된다.

initialization on demand holder idiom

기존의 java singleton pattern 이 가지고 있는 문제들을 해결하기 위해 새로운 singleton pattern이 제시되었다. 바로 위의 Initialization on demand holder idiom 기법이다. 이 기법은 jvm의 class loader의 매커니즘과 class의 load 시점을 이용하여 내부 class를 생성시킴으로 thread 간의 동기화 문제를 해결한다. 또한 laza initialization 이 가능하며 모든 java 버전과, jvm에서 사용이 가능하다. 현재 java 에서 singleton 을 생성시킨다고 하면 거의 이 기법을 사용한다고 보면 된다.


* 참고



반응형