[Design Pattern] 싱글턴 패턴 (Singleton Pattern)

Date:     Updated:

카테고리:

태그:

싱글톤 패턴이란?

싱글톤 패턴이란 단 하나의 유일한 객체를 만들기 위한 코드 패턴 입니다.

메모리 절약을 위해, 인스턴스가 필요할 때 똑같은 인스턴스를 새로 만들지 않고, 기존의 인스턴스를 가져와 활용합니다.

또한, 하나의 값만을 참조해야 할 때, 싱글톤 패턴을 사용합니다.

싱글톤 패턴의 장점

  • 인스턴스가단 하나이기 때문에 인스턴스를 생성할 때 드는 비용이 줄어듦
  • 싱글톤으로 만들어진 클래스의 인스턴스는 전역 인스턴스 이기 때문에 다른 클래스의 인스턴스들이 데이터를 공유할 수 있음

싱글톤 패턴의 단점

  • 싱글톤 인스턴스를 여러곳에서 많이 공유할 경우, 다른 클래스의 인스턴스 간 의존성이 높아짐
  • 의존성이 높아지면 테스트하기 힘듦
  • 멀티스레드 환경에서 오류가 날 수 있음(동기화로 해결할 수 있긴 함)

싱글톤 패턴은 고전적인 형태가 있고, 그 형태의 문제점들을 개선해 나가며 새롭게 구성된 형태들이 있습니다.

우선, 고전적인 패턴부터 하나씩 만들어 봅니다.

예제 - 고전적인 싱글턴 패턴 구현

고전적인 싱글턴패턴은 단순합니다.

객체의 인스턴스 변수를 하나 지정하고, 그 변수를 getter 메소드로 호출하면서, 객체의 유일성을 보장하는 방식입니다.

그리고, 다른 외부에서 해당 클래스를 생성하면 안되므로, 생성자를 private으로 만들어 줍니다.

🗅 class Main


public class ClassicSingleton {  
    private static ClassicSingleton uniqueInstance;  
  
    private ClassicSingleton(){}  
  
    public static ClassicSingleton getInstance(){  
        if(uniqueInstance == null){  
            uniqueInstance = new ClassicSingleton();  
        }  
        return uniqueInstance;  
    }  
  
    // other useful methods here  
    public String getDescription() {  
        return "싱글톤 객체 입니다.";  
    }  
}

하지만, 이런식으로 싱글턴 패턴을 구상하게 되면, 멀티스레드 문제가 생길 수 있습니다.

if(uniqueInstance == null) 이부분이 실행되고, 이제 instance 를 생성하게 될때, 다른 스레드에서 널 체크를 하게 되면, 객체가 2개가 생겨버리는 것입니다.

다음 사진과 같은 경우 입니다.

싱글턴 멀티스레드 문제

예제 - getInstance() 동기화

싱글턴의 멀티 스레드 문제를 해결하기 위한 방법으로 getInstance() 메서드를 동기화 하는 방법이 있습니다.

구현은 다음과 같이 합니다. 🗅 class ThreadSafeSingleton

public class ThreadSafeSingleton {  
    private ThreadSafeSingleton (){};  
    private static ThreadSafeSingleton Instance;  
      
    //synchronized 키워드를 넣어서 멀티스레드 문제를 해결  
    //synchronized 키워드를 넣으면 한 스레드가 메소드 사용을 끝내기 전까지 다른 스레드는 기다림  
    //하지만 동기화에서의 속도저하가 일어날 수 있음  
    //성능이 100배정도 저하될 수 있지만, 속도가 문제가 아니라면 걍 사용하면 됨  
    public static synchronized ThreadSafeSingleton getInstance(){  
        if(Instance == null){  
            Instance = new ThreadSafeSingleton();  
        }  
        return Instance;  
    }  
}

syncronized키워드를 넣으면 한 스레드가 메소드 사용을 끝내기 전까지 다른 스레드는 기다리게 됩니다.

하지만, 기다린다 라는 점 떄문에 동기화로 멀티스레드 문제를 해결할 때 다음과 같은 문제가 발생할 수 있습니다.

getInstance() 동기화의 문제점

  • 동기화에서의 속도저하가 일어날 수 있음
  • 속도저하는 약 100배 정도가 됨.

이를 해결 하는 방법에는 2가지가 있습니다.

예제 - 더 효율적으로 멀티 스레드 문제 해결

Instance 미리 생성

첫번째 방법은 정말 간단합니다.

null체크를 하지 않고, 미리 인스턴스를 만들어 놓는 것입니다.

🗅 class StaticSingleton

public class StaticSingleton {  
  
    //인스턴스가 필요할 때는 생성하지말고 처음부터 만드는게 좋음  
    //synchroized 를 안쓰고 구현하려면 이렇게 하면 됨  
    private static StaticSingleton Instance = new StaticSingleton();  
  
    private StaticSingleton(){}  
  
    public static StaticSingleton getInstance(){return Instance;}  
  
}

하지만, 여기에도 문제가 있습니다.

미리 객체를 생성해서 속도를 향상시킨 것은 좋지만, 말그대로 언제 사용할지 모를 인스턴스를 만들어 놓는 것은 메모리 낭비일 수 있습니다.

그래서, 다른 해결법이 등장합니다.

예제 - DCL 을 사용해서 동기화 되는 부분 줄이기

DCL(Double-Checked Locking)을 사용하면 인스턴스가 생성되어 있는지 확인한 다음 생성되어 있지 않았을 때만 동기화할 수 있습니다.

이러면, 처음에만 동기화 하고 나중에는 동기화하지 않아도 됩니다.

🗅 class DCLSingleton

public class DCLSingleton {  
  
    //volatile 키워드를 사용하면 멀티스레딩을 쓰더라도,  
    // uniqueInstance 변수가 Singleton 인스턴스로 초기화되는 과정이 올바르게 진행됨  
    //이런식으로 구현하면, getInstance()메소드를 사용할 때 발생하는 속도를 극적으로 줄일 수 있음  
    //dcl 은 자바 1.4 버전 이전에는 사용할 수 없음  
    private volatile static DCLSingleton uniqueInstance;  
  
    private DCLSingleton (){}  
  
    public static DCLSingleton getInstance(){  
        if(uniqueInstance == null){  
            //인스턴스가 있는지 확인하고, 없으면 동기화된 블록으로 들어감  
            //이렇게하면 처음에만 동기화 됨  
            synchronized (DCLSingleton.class){  
                if(uniqueInstance == null){  
                    uniqueInstance = new DCLSingleton();  
                }  
            }  
        }  
        return uniqueInstance;  
    }  
}

volatile키워드를 사용하면 멀티스레딩을 하더라도, uniqueInstance 변수가 Singleton 인스턴스로 초기화되는 과정이 올바르게 진행 됩니다.

이런식으로 구현하면, getInstance()메소드를 사용할 때 발생하는 속도 저하를 극적으로 줄일 수 있습니다.

하지만, 여기에도 단점이 존재합니다.

java 5 보다 낮은 버전의 JVM을 써야 한다면, 다른 방식으로 싱글턴을 구현해야 합니다.

예제 - Enum을 활용한 싱글톤

지금까지 논의한 모든 문제를 enum으로 싱글턴을 생성해서 해결 할 수 있습니다.

public enum Singleton {  
    UNIQUE_INSTANCE;  
  
    // other useful fields here  
  
    // other useful methods here    
    public String getDescription() {  
        return "I'm a thread safe Singleton!";  
    }  
         
}

정말 간단하게 구현이 됩니다.

하지만 해당 문법은 java에서만 동작합니다.

그래서 각 언어에서 싱글톤을 안전하게 쓰기 위한 방법들을 찾아보는 것이 중요할 것 같습니다.

그래도 역시, 싱글턴은 객체지향의 철학에도 어긋나고, 단단한 결합을 만들어내어 테스트에도 불리한 점이 있기에 자주 사용하지 않는 것이 좋습니다.

Design Pattern 카테고리 내 다른 글 보러가기

댓글 남기기