본문 바로가기
Spring

Singleton 패턴 활용!

by 너츠너츠 2021. 12. 24.

싱글톤 패턴이란 클래스의 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴입니다.

Client가 Service를 요청할 때마다 새롭게 인스턴스가 생성되는 현상

위의 그림과 같이 보통 웹 애플리케이션은 여러 클라이언트로 부터 동시에 요청이 들어오곤 합니다.

예를 들어 배달을 주문 하는 경우 배달앱에서는 여러 유저가 동시에 배달을 주문하기 때문에 결제서비스가 새롭게 생성되면 메모리가 낭비가 심할 뿐더러 문제가 생길 수도 있습니다.

따라서 이러한 현상을 해결하기 위해 해당 객체를 딱 한개만 공유하도록 설계한 것이 싱글톤 패턴입니다.

 

Singleton 패턴을 통해 instance를 전달하는 그림

위의 그림이 싱글톤 패턴을 적용하여 하나의 인스턴스만을 생성하고 전달하는 것입니다.

 

1. Eager Initialization

public class SingletonPattern {
    
    // 1. static 영역에 객체를 1개만 생성합니다.
    private static final SingletonPattern instance = new SingletonPattern();
    
    // 2. instance가 필요하다면 getInstance를 통해 받을 수 있도록 합니다.
    public static SingletonPattern getInstance(){
        return instance;
    }
    
    // 3. 생성자를 private으로 선언함으로서 외부에서 이 class를 생성할 수 없도록 막습니다.
    private SingletonPattern(){}
}

싱글톤 패턴은 위의 형식처럼 간편하게 해결될 수 있습니다. 이것은 Eager Initialization이라고 불립니다. 

 

하지만 클래스 로딩 시점에 초기화되어 인스턴스가 필요하지 않은 경우에도 생성됩니다. 이러한 문제점은 Lazy Initialization을 통해 해결가능합니다.

 

2. Lazy Initialization

public class SingletonPattern {

    // 1. static 영역에 객체를 1개만 생성합니다.
    private static SingletonPattern instance;

    // 2. instance가 필요하다면 getInstance를 통해 받을 수 있도록 합니다.
    public static SingletonPattern getInstance(){
        if(instance == null) instance = new SingletonPattern();
        return instance;
    }

    // 3. 생성자를 private으로 선언함으로서 외부에서 이 class를 생성할 수 없도록 막습니다.
    private SingletonPattern(){}
}

이렇게 null일 경우에만 instance를 생성하는 것을 Lazy Initialization이라고 부릅니다. 하지만 이것 또한 문제가 있는데요 여러 쓰레드를 돌릴 경우 인스턴스가 여러개 생길 수 있다는 문제점이 있습니다. 이러한 문제점은 getInstance 함수 앞에 Synchronized로 동기화 하는 방법이 있습니다.

ex) 

public synchronized static SingletonPattern getInstance(){}

 

하지만 synchronized를 이용하는 방식 역시 성능적으로 효율적이지 않기 때문에 DCL(Double Checked Locking)기법을 이용합니다.

public static SingletonPattern getInstance(){
        if(instance == null){
            synchronized (SingletonPattern.class){
                if(instance == null)
                    instance = new SingletonPattern();
            }
        }
        return instance;
    }

DCL이 보기에 좋아보일 수 있으나 쓰레드의 충돌로 인하여 쓰레드1이 getInstance메서드의 첫번째 if문을 실행해 인스턴스를 위한 메모리를 할당하고 초기화하는 과정에서 쓰레드2가 인스턴스를 조회하게 된다면 첫번재 if문에서 인스턴스가 할당된 것을 확인하고 아직 제대로 초기화되지 않은 인스턴스를 반환받게 됩니다. 이러한 경우에 문제가 생길 수 있습니다. 이런 문제를 해결하기 위해선 간단하게 volatile를 사용해주면 됩니다.

// 1. static 영역에 객체를 1개만 생성합니다.
    private volatile static SingletonPattern instance;

※ 여기서 voliatile이란 

  • Java변수를 Main Memory에 저장하겠다고 명시하는 것입니다.
  • 매번 변수를 쓰거나 불러올 때 CPU cache가 아닌 Main Memory에서 읽고 쓰는 것입니다.
  • volatile을 사용해야하는 상황이라면 synchronized를 통해 변수의 원자성을 보장해주는 것이 필요합니다.

 

3. Initialization on demand holder idiom

마지막으로 demand holder방식이 가장 많이 사용되는 싱글턴 구현 방식입니다. volatile이나 synchronized 없이도 동시성 문제를 해결할 수 있습니다.

 

public class SingletonPattern {
    // 1. class 내부에 static LazyHolder class를 만들어줍니다.
    private static class LazyHolder{
        public static final SingletonPattern instance = new SingletonPattern();
    }
    
    // 2. instance가 필요하다면 getInstance를 통해 받을 수 있도록 합니다.
    public static SingletonPattern getInstance(){
        return LazyHolder.instance;
    }

    // 3. 생성자를 private으로 선언함으로서 외부에서 이 class를 생성할 수 없도록 막습니다.
    private SingletonPattern(){}
}

 

반응형

댓글