FIF's 코딩팩토리

싱글톤 패턴(Singleton Pattern) 정리 본문

Back-End/Design Pattern(디자인 패턴)

싱글톤 패턴(Singleton Pattern) 정리

FIF 2019. 6. 5. 15:18
반응형

싱글톤 패턴(Singleton Pattern) 정의

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

 

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

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

 

싱글톤 클래스 다이어그램

 

고전적인 싱글톤 패턴 구현의 문제점

uniqueInstance에 하나밖에 없는 인스턴스가 저장돼있다.

uniqueInstance가 null이기 때문에 아직 인스턴스가 만들어지지 않았다면 private으로 선언된 생성자를 이용해서 Singleton객체를 만든 다음 uniqueInstance 변수에 객체를 대입한다.

이렇게 하면 인스턴스가 필요한 상황이 오기 전까지 인스턴스는 생성되지 않는다.

이런 방법을 게으른 인스턴스 생성(Lazy Instance)라 부른다.

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

겉보기엔 문제가 없는 거 같지만 멀티스레드 상황에서 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, 새로운 인스턴스 생성

 

결과적으로 서로 다른 두 객체가 리턴돼버렸다가 두 개가 만들어지는 오류

 

해결방법

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.     인스턴스를 필요할 때 생성하지 말고, 처음부터 만들어 버린다.

애플리케이션에서 반드시 Singleton의 인스턴스를 생성하고, 그 인스턴스를 항상 사용한다면, 또는 인스턴스를 애플리케이션 실행 중에 수시로 만들고 관리하기가 귀찮다면 처음부터 Singleton 인스턴스를 만들어버리는 것도 괜찮은 방법이다.

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

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

 

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;
      }
 }

 

멀티스레딩을 쓰더라도 uniqueInstance변수가 Singleton 인스턴스로 초기화되는 과정이 올바르게 진행되도록 할 수 있다.

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

반응형
Comments