본문 바로가기

Coding Note

[Design pattern] Singleton

Singleton Pattern : 프로그램 라이프사이클 동안 하나의 클래스에 하나의 인스턴스생성만을 허락하는 패턴이다.

 

 

 

Singleton은 개념자체는 복잡하지 않으므로 실질적인 예를 들어 설명하겠다.

 

/* Singleton.java */

public class Singleton {

private static Singleton instance = new Singleton();

private Singleton(){ }


public static Singleton getInstance(){

return instance;

}

 

//멤버변수 및 메소드

}

기본적인 Singleton이다. Singleton으로 구현하고자 하는 클래스의 생성자를 외부에서 접근하지 못하게 하며 자신의 인스턴스 하나를 정적으로 생성해둔다. 그 후 외부에서의 접근은 Singleton.getInstance() 와 같은 접근을 통해 유일한 Singleton 인스턴스를 받아오게된다. 이렇게 외부에서의 생성을 막고 오직 하나의 인스턴스에만 접근하게 하여 Singleton클래스는 두개 이상의 인스턴스가 생성될 수 없게된다.


하지만 static 키워드가 의미하듯 Singleton 인스턴스는 정적인 컴파일시간에 이미 만들어져버리므로 실행중에 다른 처리가 필요하다면 조금 다른 방법이 필요하다.

 

/* Singleton2.java */

public class Singleton2 {

private static Singleton2 instance = null;


private int randomNumber;


private Singleton2(){}


public static Singleton2 getInstance(){

if(instance == null){

instance = new Singleton2();


Random random = new Random();

instance.randomNumber = random.nextInt(10)+1;

}

return instance;

}


public int getRandomNumber(){ return randomNumber; }

}

 

Singleton클래스와 비교해보면 Singleton2클래스는 'instance'의 생성을 getInstance() 메소드에서 진행한다. 이러한 생성을 게으른 인스턴스화(Lazy instantiation) 이라고 한다. 만약 Singleton2 클래스를 한번도 생성하지 않는다면 이 클래스에 대한 인스턴스는 하나도 생성되지 않는것이다.

 

재미있는 부분은 static 메소드인 getInstance() 안에서 non-static 변수나 함수에 접근할 수 없다. 그러므로 'instance'변수를 인스턴스화 한 후 instance.MemberSomthing  와 같은 방법으로 자신의 멤버에 접근해줘야한다. 'instance'는 자기의 멤버이므로 instance의 멤버들은 private멤버에도 접근할 수 있다.


두번째 방법이 훨씬 스마트해보이지만, 멀티스레드환경에선 또다른 문제가 발생한다.


다른 스레드에서 Singleton2.getInstance()  를 동시에 호출하게 될 경우,


if(instance == null){

instance = new Singleton2();


Random random = new Random();

instance.randomNumber = random.nextInt(10)+1;

}


이 루틴이 중복수행될 수 있다. 그럼 Singleton2에 대한 인스턴스가 두개가되버린다.


이를 해결하기 위해 Java가 제공하는 synchronized 기능을 사용할 수 있다.

/* getInstance() method in class Singleton2 ... */

public static synchronized Singleton2 getInstance(){

if(instance == null){

instance = new Singleton2();


Random random = new Random();

instance.randomNumber = random.nextInt(10)+1;

}

return instance;

}


하지만 synchronized 는 동기화를 위해 상당한 비용을 소모하므로 동기화가 정말 필요한지에 대한 고민을 많이 해본 후 넣어줘야한다. synchronized를 보다 효율적으로 활용한다면 다음과 같이 사용할 수 있다.

 

/* getInstance() method in class Singleton2 ... */

public static Singleton2 getInstance(){

if( instance == null ){

//인스턴스화 직전의 작업만 동기화시킨다.

synchronized(Singleton2.class){

if( instance == null ){

instance = new Singleton2();

 

Random random = new Random();

instance.randomNumber = random.nextInt(10) + 1;

}

}

}

return instance;

}

 
Singleton은 하나의 인스턴스만을 허용하기 때문에 앞서본 Factory class와도 자주 결합된다. 인스턴스를 뽑아내는 Factory가 여러개일 필요가 없으니말이다.

 

c++로 가면 Singleton에 정적 초기화를 통한 인스턴스 생성부터 죽었다 살았다 하는 피닉스싱글톤 까지,, java에서의 방법론보다 더 많은 구현방법을 가진다. 객체의 '소멸'에 대한 이슈때문인데 아직 완벽한 싱글톤이 없다 할 정도로 발전이 많이 필요한 부분이다.

한줄요약 : Singleton - 2.독신자(결혼을 안 했거나 애인이 없는 사람) [출처 네이버사전]. 슬픈패턴

'Coding Note' 카테고리의 다른 글

[Design pattern] Prototype  (1) 2013.02.01
[Design pattern] Builder  (0) 2013.01.31
[Design pattern] Factory Method  (0) 2013.01.30
[Design pattern] Abstract Factory  (0) 2013.01.27
[Design pattern] Observer  (4) 2013.01.22