본문 바로가기

Design Pattern

[디자인 패턴_Again] Singleton Pattern.

너무나 잘 알려진 패턴입니다.

단 하나의 인스턴스를 유지하는 것입니다.

아마 현장에서도 가장 많이 쓰이고 있는 패턴 중 하나가 아닐까 싶습니다.

생성자를 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 를 사용하셔도 동기화 문제는 처리 하실 수 있습니다.