BACKEND/JAVA & SPRING

싱글톤 패턴이란?

이-프 2023. 9. 26. 11:20

싱글톤 패턴이란?

  • 단 하나의 인스턴스를 생성해 사용하는 디자인 패턴이다.
  • 인스턴스가 1개만 존재해야 한다는 것을 보장하고 싶은 경우와
  • 동일한 인스턴스를 자주 생성해야 하는 경우에 주로 사용한다.

왜 싱글톤 패턴을 사용하는가?

  • 인스턴스를 하나만 사용한다면 메모리 낭비를 방지할 수 있다.
  • 다른 클래스 간의 데이터 공유가 쉽다.

    └ 싱글톤으로 생성된 객체는 전역성을 띄기 때문에 다른 객체와 공유가 쉽다.

싱글톤 패턴의 문제점

  • 싱글톤 패턴 구현을 위한 코드가 많이 필요하다.
  • 테스트하기 어렵다.

    └ 자원을 공유하기 때문에 테스트시, 매번 인스턴스의 상태를 초기화 해줘야한다.

  • 싱글톤 객체간의 결합도가 높아져 객체 지향 설계 원칙 중 개방-폐쇄의 원칙에 어긋나게 된다.

싱글톤 패턴 구현 방식

Lazy Initialization (늦은 초기화)

  • static 한 자기자신의 클래스를 필드로 만들고, 인스턴스가 필요하여 요청할 때 생성되는 형태
  • 멀티 쓰레드 환경에 취약하다.
💡
문제점
public class Singleton {
 
    private static Singleton instance;
 
    private Singleton() {}
 
    public static Singleton getInstance() {
        
        if (instance == null) {
            instance = new Singleton();
        }
        
        return instance;
    }
}
  • 쓰레드 A가 getInstance() 메소드의 if문을 지날 때 instance가 null이면 Singleton 인스턴스를 생성한다.
  • 이때, 갑자기 쓰레드 B로 제어권이 넘어가면 쓰레드 B역시 if문이 수행되고 쓰레드 B또한 Singleton 인스턴스를 생성하게 된다.

⇒ 결국엔 인스턴스가 두번 생성되는 문제점 o

Synchronized (늦은 초기화 해결 방안)

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

Synchronized : 쓰레드가 자원을 사용한다면 다른 쓰레드가 해당 자원을 사용하지 못하도록 lock을 거는 방법

  • 멀티쓰레드의 동시 접근 문제가 해결된다.
  • 하지만, 매번 리턴 받을 때마다 쓰레드를 동기화함으로써 성능저하가 된다.

Eager Initialization (이른 초기화)

public class Singleton {
 
    private static Singleton instance = new Singleton();
 
    private Singleton() {}
 
    public static Singleton getInstance() {
        return instance;
    }
}
  • 멀티 쓰레드 환경에서 유발되는 모든 문제를 해결한다.
  • 쓰레드가 getInstance를 호출하는 시점이 아닌, Class가 로딩되는 시점 (static 영역의 데이터 로딩 시점에 인스턴스 생성) = 하나의 인스턴스만 생성되는 것을 보장해준다.
  • 미리 만들어 놓은 인스턴스를 리턴하므로 Thread-safe함과 동시에 간결하고 성능도 좋다
  • 하지만, 인스턴스를 사용하지 않는다면 메모리 낭비에 불과하다.

DCL(Double Checked Locking)

public class Singleton {
 
    private volatile static Singleton instance;
 
    private Singleton() {}
 
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
  • getInstance() 메소드 레벨에 synchronized가 있지 않고 메소드 내부에 존재한다.
    • 호출할 때마다 synchronized가 걸리지 않고, 인스턴스가 이미 존재한다면 synchronized를 쓰지 않기 대문에 성능 이슈를 피할 수 있다.
  • static 필드에 volatile 키워드를 쓴다.
💡
volatile이란?
  • Java 변수를 Main Memory에 저장하겠다.
  • 멀티 쓰레드 환경에서 하나의 쓰레드만 Read & write이 가능하고, 나머지 쓰레드가 read만 하여 가장 최신의 값을 보장하도록 한다.
  • Cache에 저장된 값이 아닌 Main Memory에서 읽고, 작성도 Main Memory에 작성한다.
💡
왜 volatile 키워드를 사용해야하는가?
  • 작업을 수행하는 동안 성능 향상을 위해 각 메모리에서 읽은 변수를 CPU Cache에 저장하게 된다. 그리고 쓰레드가 변수값을 읽어오면 각 CPU Cache에서 읽기에 변수 값이 불일치되는 문제가 발생한다.
  • 그래서 volatile을 추가해서 공통된 Main Memory에 저장하고 읽게끔하여 변수 값 불일치 문제를 해결한다.

Lazy Holder

public class Singleton {
 
    private Singleton() {}
 
    //inner class
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
 
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}
  • Lazy initialization 방식을 가져가면서 쓰레드간 동기화문제를 동시에 해결할 수 있다.
  • SingletonHolder는 getInstance()메소드가 호출되기 전에는 참조되지 않으며, 최초로 메소드가 호출될 때 클래스 로더에 의해 Singleton 인스턴스를 생성하여 리턴한다.
  • SingletonHolder 내부 인스턴스는 static 이기 때문에 클래스 로딩 시점에 한번만 호출된다는 점을 이용한 것이며, final을 사용해 다시 값이 할당되지 않도록 한다.

⇒ volatile이나 synchronized와 같은 키워드가 없어도 Thread-safe 하면서 성능도 보장하는 아주 훌륭한 방법이다.


Uploaded by N2T

'BACKEND > JAVA & SPRING' 카테고리의 다른 글

@Controller vs @RestController  (0) 2023.10.31
카카오맵 open API - 키워드 검색하기  (0) 2023.10.31
객체지향 프로그래밍이란?  (0) 2023.09.26
TestCode 작성법  (0) 2023.09.18
Entity와 DTO의 연관관계  (0) 2023.09.18