싱글톤 패턴

taeheon95·2021년 9월 7일
0

디자인 패턴

목록 보기
3/5
post-thumbnail

싱글톤 패턴이란?

개발을 하다보면 어떤 객체에 대한 접근이 반드시 하나의 객체로만 접근해야하는 상황이 있습니다. 예를 들면 데이터 베이스와 같이 정보 저장하는 할 때 두가지 객체에서 각각 따로 접근하게 될 경우 데이터의 일관성이 깨질 수 있게 됩니다. 또는 실제 하드웨어에 대한 접근을 할 경우 두가지의 객체 접근으로 인하여 예기치 못한 동작이 일어날 수 있게 될 상황이 있습니다.

싱글톤 패턴은 위와 같이 데이터의 일관성이나 동작의 일관성이 깨질 수 있는 상황에서 데이터의 일관성 또는 동작의 일관성을 보장하기 위해 사용하는 디자인 패턴입니다.

싱글톤 패턴 자체는 어렵지 않지만 실제 싱글톤을 만들 때는 동시성 문제를 생각하면서 싱글톤을 설계해야 합니다.

자바 코드로 구현한 간단한 싱글톤 패턴

싱글톤 패턴의 공통적인 특징은 private constructor과 static method라고 할 수 있습니다.

이른 초기화 방식 싱글톤 패턴(Thread-Safe)

public class singleton{
    //이른 초기화 방식의 싱글톤
    private static Singleton instance = new Singleton();
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        return instance
    }
}

이른 초기화 방식은 static 키워드를 통해 프로그램이 시작하는 순간에 정적바인딩을 통해 인스턴스를 초기화 하여 인스턴스를 싱글톤화하여 사용하는 방식으로 기본적으로 multi-thread를 사용하게 된다고 하더라도 안전을 보장할 수 있습니다.

가장 단순하면서도 안전한 방식이지만 이 방식의 문제점이 있습니다. 만약 객체에 대한 접근이 늦어지게 될 경우 객체가 차지하고 있는 메모리 만큼이 낭비된다는 문제점이 있습니다.

늦은 초기화 방식 싱글톤 패턴(Thread-Unsafe)

이른 초기화 방식의 단점인 사용하지 않는 객체를 미리 메모리에 올려둔다는 단점을 보안한 방식으로서 늦은 초기화를 통하지만 thread-safe하지 않습니다.

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

이 방식의 경우 두 객체가 동시에 혹은 아주 잠깐의 틈을 두어 접근하여 instance가 null이라고 판단한 경우 두 개의 instance 객체가 생성되어 문제점이 생기게 됩니다. 싱글톤 패턴의 기본이 하나의 객체를 통한 제한적 접근이 깨지게 됩니다.

늦은 초기화 + 동기 방식 싱글톤 패턴(Thread-Safe)

이른 초기화 방식의 단점인 사용하지 않는 객체를 미리 메모리에 올려둔다는 단점과 일반적인 늦은 초기화 방식의 싱글톤 패턴을 보안한 방식으로서 thread 동기화 메소드를 통하여 객체가 유일함을 보장하는 방식입니다.

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

동기화 블록 synchronzied를 통해 사용한 늦은 초기화 방식의 싱글톤 패턴으로서 기본적으로 thread-safe가 어느정도 보장되지만 생성된 싱글톤 패턴에 접근할 때도 동기화 블록을 통해서 접근하기 때문에 싱글톤 객체를 얻게 될 때 다른 모든 thread를 멈추게 됨으로 성능이 많이 떨어지는 결과를 낳게 됩니다.

늦은 초기화. Double Checking Locking(DCL, Thread-Safe)

위에 늦은 초기화 + 동기 방식을 보안한 싱글톤 패턴으로서 인스턴스가 생성되지 않은 경우에만 동기화 블럭이 실행되게끔하여 성능 문제와 동기화 문제를 해결한 방식입니다.

public class Singleton {
    private volatile static Singleton instance;

    private Sigleton() {}

    // 늦은 초기화 DCL
    public Singleton getInstance() {
      if(uniqueInstance == null) {
         synchronized(Singleton.class) {
            if(instance == null) {
               instance = new Singleton(); 
            }
         }
      }
      return uniqueInstance;
    }
}

위 코드에서 Volatile 키워드가 등장합니다. Volatile 키워드를 통하여 멀티 쓰레딩을 사용하더라도 instace가 singleton 인스턴스로 올바르게 초기화되게 합니다.

volatile 키워드와 사용한 이유는?

volatile 키워드는 해당 객체를 cpu cache로 올리지 못하게 하고 Main Memory에 계속 상주하게 만드는 키워드입니다.

실제 변수는 변수의 사용 시점에서 cpu의 레지스터로 등록되는 과정을 거치게 됩니다. 이 과정에서 기존의 메인 메모리 영역은 비어 있기 때문에 프로그램은 싱글톤 instace가 없다고 판단하여 싱글톤 instance를 하나 더 만들게 되는 일이 발생합니다.

volatile 키워드 변수는 실제 변수가 cpu의 레지스터에 등록되지 않고 메인 메모리에 계속 상주해있기 때문에 싱글톤 instance가 새로 생기게 되는 일을 방지합니다.

그러나 이 방식 또한 동기화 메소드를 사용하는 점과 Volatile 키워드는 변수를 Main Memory 영역에 상주시키는 점 때문에 성능 상으로 떨어지는 결과를 낳습니다.

늦은 초기화, LazyHolder(Thread-safe)

늦은 초기화 방식을 변형한 방식으로서 클래스 안에 정적 멤버 클래스를 두어서 동시성 문제를 해결하는 방식으로 동기화 키워드와 volatile 키워드를 안 쓰기 때문에 성능 상의 문제점을 해결한 방식입니다.

public class Singleton {
    private Singleton() {}
    
    private static class InnerClass() {
        private static final Singleton instance = new Singleton();
    }
    
    public static Singleton getInstance() {
        return InnerClass.instace;
    }

이렇게 내부 정적 멤버 클래스를 통하여 초기화를 할 경우 내부 정적 멤버 클래스는 한 번만 생성이 되기 때문에 싱글톤 instance가 하나만 생성되는 것을 보장합니다.

또한 내부 정적 클래스는 메모리 상에 호출 시점에 인스턴스가 생성되기 때문에 동적 바인딩의 효과를 누리면서도 thread-safe하고 성능이 뛰어납니다.

profile
계속 공부하는 개발자

0개의 댓글