싱글턴 패턴 (singleton pattern)


해당 클래스의 인스턴스가 하나만 만들어지고, 어디서든지 그 인스턴스에 접근할 수 있도록 하기 위한 패턴.



클래스에서 자신의 단 하나뿐인 인스턴스를 관리하도록 만든다.

다른 어떤클래스에서도 자신의 인스턴스를 추가로 만들지 못하도록 해야한다. 


싱글턴 클래스 다이어그램




고전적인 싱글턴 패턴 구현법 (어떤 문제가 있을까?)


public class Singleton {

private static Singleton uniqueInstance;

private Singleton(){}

public static Singleton getInstance(){

if (uniqueInstance == null){

uniqueInstance = new Singleton();

}

return uniqueInstance;

}

 }


uniqueInstance에 하나 밖에없는 인스턴스가 저장, uniqueInstance가 null 이여서 아직 인스턴스가 만들어지지 않닸다면 private로 선언된 생성자를 이용해서 Singleton객체를 만든다음 uniqueInstance 변수에 객체를 대입한다.

이렇게 하면 인스턴스가 필요한 상황이 닥치기 전에는 아예 인스턴스를 생성하지않게되고 이런 방법을 게으른 인스턴스 생성(lazy instantiation)이라고 부른다.



그냥 보기에는 문제가 없어 보이는데 ???


멀티스레드 상황에서 JVM이 되어보자.


두 개의 스레드에서 위의 Singleton.getInstance() 메소드를 실행시킨다고 가정해보자.


1번 스레드


public static Singleton getInstance(){ 도달! uniqueInstance : null


2번 스레드로 제어권이 넘어감


2번 스레드


public static Singleton getInstance(){ 도달! uniqueInstance : null


1번 스레드로 제어권이 넘어감


1번 스레드

if (uniqueInstance == null){ 조건을 만족한뒤


2번 스레드로 제어권이 넘어감


2번 스레드

 

if (uniqueInstance == null){ uniqueInstance 값이 아직 null 이기 때문에 조건을 만족한뒤


1번 스레드로 제어권이 넘어감


1번 스레드


uniqueInstance = new Singleton(); 제어권이 넘어간 지점부터 계속 진행

return uniqueInstance; uniqueInstance : Object1, 새로운 인스턴스 생성


2번 스레드로 제어권이 넘어감


 2번 스레드


 uniqueInstance  = new Singleton(); 제어권이 넘어간 지점부터 계속 진행

 return uniqueInstance; uniqueInstance : Object2, 새로운 인스턴스 생성



결과적으로 서로 다른 두 객체가 리턴이 되었음 : Singleton 인스턴스가 두개가 만들어짐. 오류!!!




해결할수 있는 방법은 ???



getInstance()를 동기화시키기만 하면 멀티스레딩과 관련된 문제가 간단하게 해결된다.


 public class Singleton {

private static Singleton uniqueInstance;

private Singleton(){}

public static synchronized Singleton getInstance(){

if (uniqueInstance == null){

uniqueInstance = new Singleton();

}

return uniqueInstance;

}

 }


이렇게 하면 문제가 해결되긴 하겠지만, 동기화를 하면 속도 문제가 생기지 않는가 ???

조금 더 생각해보면 동기화를 하기가 정말 아깝다는 느낌이 들것이다. 사실 동기화가 꼭 필요한 시점은 이 메소드가 시작되는 때 뿐이다.

일단 uniqueInstance 변수에 Singleton 인스턴스를 대입하고 나면 굳이 이 메소드를 동기화된 상태로 유지시킬 필요가 없어진다는 것.

첫 번째 과정을 제외하면 동기화는 불필요한 오버헤드만 증가시킬뿐이다.




더 효율적인 방법은 없을까??


1. getInstance()의 속도가 그리 중요하지 않다면 그냥 둔다.

    - getInstance() 메소드가 애플리케이션에 큰 부담을 주지 않는다면 그냥 놔둬도 된다. 

      getInstance()를 동기화시키는게 그리 어려운 일도 아니고, 효율 면에서도 괜찮을수 있다.

      단. 메소드를 동기화하면 성능이 100배 정도 저하된다는 것은 기억해 두자.

      만약 getInstance()가 애플리케이션에서 병목으로 작용한다면 다른 방법을 생각해보자.




2. 인스턴스를 필요할 때 생성하지 말고, 처음부터 만들어 버린다.


 public class Singleton {

private static Singleton uniqueInstance = new Singleton();

private Singleton(){}

public static synchronized Singleton getInstance(){

return uniqueInstance;

}

 }


     - 애플리케이션에서 반드시 Singleton의 인스턴스를 생성하고, 그 인스턴스를 항상 사용한다면, 

       또는 인스턴스를 실행중에 수시로 만들고 관리하기가 성가시다면 처음부터 Singleton인스턴스를 

       만들어 버리는 것도 괜찮은 방법이다.

       이런 접근법은 클래스가 로딩될 때 JVM에서 Singleton의 유일한 인스턴스를 생성해준다.



3. DCL(Double-Checking Locking) 을 써서 getInstance() 에서 동기화되는 부분을 줄인다.


  public class Singleton {

private volatile static Singleton uniqueInstance;

private Singleton(){}

public static Singleton getInstance(){

if (uniqueInstance == null){

                        synchronized (Singleton.class){

                                  if(uniqueInstance == null){

                                             uniqueInstance = new Singleton();

                                  }

                        }

}

return uniqueInstance;

}

 }



volatile 키워드를 사용하면 자바의 일종의 최적화인 리오더링(보통 컴파일 과정에서 일어나며, 프로그래머가 만들어낸 코드는 컴파일 될 때 좀더 빠르게 실행될 수 있도록 조작이 가해져 최적하됨)을 회피하여 읽기와 쓰기순서를 보장 멀티스레딩을 쓰더라도 uniqueInstance변수가 Singleton 인스턴스로 초기화 되는 과정이 올바르게 진행되도록 할 수 있다.

하지만!! DCL은 자바1.5이상의 버전에서만 사용가능하다.

자바 1.4 및 그 전에 나온 버전의 JVM 중에는 volatile 키워드를 사용하더라도 동기화가 잘 안되는 것이 많다. 일종의 버그.



아래의 링크를 참조해보면 Double-Checking Locking 과 Singleton 패턴에대해 좀더 상세히 나와있다.

결론은 모든 JVM구현에서 작동하는 걸 보장할수 없기때문에 그리고 뭐라뭐라.. 때문에 DCL은 사용하지 않는게 좋다인데..

그냥 아 그렇구나 옛날 JVM은 그랬구나 하면서 읽어볼수있다. 

http://gampol.tistory.com/m/post/entry/Double-checked-locking%EA%B3%BC-Singleton-%ED%8C%A8%ED%84%B4













참고.


Head First Design patterns.



출처: http://jusungpark.tistory.com/16 [정리정리정리]

복사했습니다!