Singleton Pattern

Singleton 패턴은 흔히 사용되는 생성 패턴의 하나이다.
이는 하나의 클래스에서 오직 하나의 인스턴스만 생성되도록 보장하는 기법을 기술한다.
즉, 클래스 외부의 누구도 객체의 인스턴스를 생성하지 못하게 하는 접근법을 사용한다.
이 접근법은 다양한 방식으로 구현이 가능하다.

아래는 가장 대표적인 구현 방식이다.

아마 현장에서도 가장 많이 쓰이고 있는 패턴 중 하나가 아닐까 싶습니다.
생성자를 private으로 선언하여 다른 클래스에서 이 클래스의 객체를 직접 생성시키지 못하게 하고, static 으로 선언 된 자기 자신의 객체를 넘겨주는 메소드를 만들어 이미 생성되어 있는 자기 자신의 객체를 넘겨주는 방식입니다.

public class Singleton {
   private static Singleton instance;
 
   private Singleton() { }
 
   public static Singleton getInstance() {
      if(instance == null) {
         instance = new Singleton();
      }
 
      return instance;
   }
}

일반적으로 사용되는 싱글턴 패턴의 형식입니다.
getInstance  메서드는 Singleton 인스턴스가 있으면 그것을 리턴하고 없으면, 새로 생성하여 리턴합니다.
하지만, 위와 같은 경우에는 문제가 생길 수 있습니다.

다수의 스레드가 getInstacne 메소드를 실행하는 경우 instance 객체가 2개 이상 생성 될 수 있기 때문입니다.

예를 들어 쓰레드 A와 B가 getInstance 메소드를 거의 동시에 실행 되었을 경우 jvm의 스케쥴링에 따라서 어떤 경우에는 아래와 같은 현상이 발생 할 수 있습니다.

쓰레드 A가 instance == null 임을 확인

쓰레드 A wait

쓰레드 B가 instance == null 임을 확인

쓰레드 B가 instance = new Singleton(); 를 실행

쓰레드 B가 return instance로 객체를 리턴.

쓰레드 A가 instance = new Singleton(); 를 실행

쓰레드 A가 return instance로 객체를 리턴.

위 처럼
서로 다른 객체가 리턴 되는 경우가 있을 수 있습니다.


이런 멀티스레딩을 해결 하는 간단한 방법 중 하나는 synchronized를 사용하는 것 입니다.

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

메소드 자체에 동기화가 걸려버리는거죠.. 하지만 위의 경우.. 멀티스레드로 인해 문제가 생기는 경우는 instance가 처음 생성 될 때 뿐입니다. 즉, 일단 instance 변수에 Singleton의 객체 레퍼런스를 대입하고 나면 이 메소드를 통채로 동기화를 시킬 필요는 없는 것입니다.

괜히 오버헤드만 증가하죠..

그래서 나온 다른 하나의 방법은 아예 처음부터 만들어버리는 것입니다.

public class Singleton {
   private static Singleton instance = new Singleton();
 
    private Singleton() { }
 
    public static Singleton getInstance() {
       return instance;
    }
}


그리고 다른 하나의 방법은 volatile 을 사용하는 것입니다.
책에서는 DCL이라고 하네요. Double-Checking Locking.

상세한 내용은
http://javaservice.net/~java/bbs/read.cgi?m=qna&b=qna2&c=r_p_p&n=1088474804
이곳에 서민구(4baf)님께서 작성하신 댓글을 읽어보시면 좋을 것 같습니다.

아무튼.. 이 volatile을 이용하면

public class Singleton {
   private volatile static Singleton instance;
 
   private Singleton() { }
 
   public static Singleton getInstance() {
      if(instance == null) {
         synchronized(Singleton.class) {
            if(instance == null) {
               instance = new Singleton();
            }
         }
      }
      return instance;
    }
}


volatile을 사용했음에도 synchronized(Singleton.class)를 사용한 이유는 안전장치라고 생각해도 될 것 같습니다.

일단 한번 인스턴스가 생성 된 이후에는 첫번째 if(instance == null) { } 이 블럭 안으로 쓰레드가 들어 갈 일이 없을테니까요.

메서드 전체에 syncronized를 걸어놓지 않았기 때문에 일단 처음 instance 변수에 Singleton 객체 레퍼런스를 대입 할 때를 제외하고는 성능의 저하도 없을 것입니다.

다만 이 방식은 자바5 부터 사용 하시길 권장합니다.

그리고 속도의 문제가 큰 이슈거리가 아니시라면 그냥 메소드 전체에 synchronized 를 사용하셔도 동기화 문제는 처리 하실 수 있습니다.


//////////////////////////////////////// 참고 ////////////////////////////////////////
인스턴스 생성 후 다시 인스턴스를 생성했을 때의 비교

싱글턴을 사용한 경우(첫번째 방식)
[04:51:47]::>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[04:51:47]::>1 : 22130853
[04:51:47]::>2 : registration.entrance.PdssMgr@151b0a5
[04:51:47]::>3 : class registration.entrance.PdssMgr
[04:51:47]::>BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
[04:51:47]::>1 : 22130853
[04:51:47]::>2 : registration.entrance.PdssMgr@151b0a5
[04:51:47]::>3 : class registration.entrance.PdssMgr

싱글턴을 사용하지 않은 경우
[04:51:47]::>CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
[04:51:47]::>1 : 3974996
[04:51:47]::>2 : registration.entrance.PdssMgrTest@3ca754
[04:51:47]::>3 : class registration.entrance.PdssMgrTest
[04:51:47]::>DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
[04:51:47]::>1 : 896033
[04:51:47]::>2 : registration.entrance.PdssMgrTest@dac21
[04:51:47]::>3 : class registration.entrance.PdssMgrTest



[출처(참고)] : http://devyongsik.tistory.com/tag/singleton

+ Recent posts