본문 바로가기
Java/DesignPattern

Singleton Pattern 싱글턴 패턴

by amungstudy 2025. 9. 7.

싱글턴 패턴의 특징

싱글턴 패턴은 클래스 인스턴스를 하나만 만들고, 그 인스턴스로의 전역 접근을 제공합니다.

- 싱글턴이 Lazy 방식으로 생성되도록 구현할 수 있습니다.

- 자원을 많이 잡아먹는 인스턴스에 특히 유용합니다 ( 애플리케이션이 시작될 때 객체가 생성되는 전역변수로 선언하는 것보다 자원을 아낄 수 있습니다.)

 

멀티스레딩 문제

- 멀티스레드 환경에서는 동시 접근 문제 때문에 의도치 않게 여러 인스턴스가 만들어질 수 있다.

- 단순히 public  접근자에서 인스턴스 유무를 가지고 판단하게 되면 인스턴스가 여러개 생성될 수 있다

class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {   // <-- 멀티스레드 환경에서 동시에 진입 가능
            instance = new Singleton();
        }
        return instance;
    }
}
  • 두 스레드가 동시에 getInstance()를 호출하면
    instance == null 조건을 둘 다 통과 → 2개의 객체 생성될 수 있음.

멀티스레딩 문제 해결법

첫번째 방법. 간단하게 synchronized 동기화 하기 >> 이렇게 락을 걸면 성능이 100배 정도 저하가 된다. 

이 부분이 병목으로 작용한다면 다른 방법을 생각해야함.

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

 

두번째 방법. 인스턴스를 처음부터 만드는 방식. >> lazy initialization(필요할 때 생성) 불가능.  

public class Singleton {
	private static Singleton uniqueInstance = new Singleton();
    // 클래스 로딩 시 JVM에서 하나뿐인 인스턴스를 생성한다. 
    
    private Singleton() {}
    
    public static Singleton getInstance() {
    	return uniqueInstance;
    }
}

 

세번째 방법.DCL(Double-Checked Locking) 을 써서 getInstance()에서 동기화되는 부분을 줄인다

class Singleton {
    private static volatile Singleton instance; // volatile 중요!

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) { // 1차 체크
            synchronized (Singleton.class) {
                if (instance == null) { // 2차 체크
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

 

  • volatile을 붙여야 JVM의 명령 재정렬 문제를 방지.
  • 처음만 synchronized 블록에 들어가고 이후엔 락이 없어 성능 좋음.

** JVM의 명령 재정렬 문제 란? 

 

네번째 방법. enum을 사용한다. >> 간단하게 동기화 제, 클래스 로딩문제, 리플렉션, 직렬화 역직렬화 문제를 해결 할 수 있음

public enum Singleton {
	UNIQUE_INSTANCE;
    // 기타 필요한 필드
}

public class SingletonClient {
	public static void main(String[] args) {
    	Singleton singleton = Singleton.UNIQUE_INSTACE;
        // 여기서 싱글턴 사용
	}
}

 

 

다섯번째 방법. Holder 패턴 (Lazy + Thread-safe)

class Singleton {
    private Singleton() {}

    private static class Holder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return Holder.INSTANCE;
    }
}

 

  • Holder 클래스는 getInstance() 호출될 때 로딩됨.
  • JVM이 클래스 로딩을 thread-safe 하게 보장하므로 안전.
  • Lazy initialization + 성능까지 잡은 방법

 

  • 성능: DCL(double-checked locking)보다 간단하고 빠름.
  •  단점
    1. 리플렉션 공격에 취약
      • Singleton.class.getDeclaredConstructor().newInstance()로 접근해서 private 생성자를 강제로 호출할 수 있습니다.
      • 그럼 Holder 안에 이미 만들어둔 인스턴스와 별개의 객체가 또 생깁니다 → 싱글턴 깨짐.
      • 리플렉션 대응 방법
        • class Singleton {
              private static boolean instanceCreated = false;
          
              private Singleton() {
                  if (instanceCreated) {
                      throw new RuntimeException("Use getInstance() method to create");
                  }
                  instanceCreated = true;
              }
          
              private static class Holder {
                  private static final Singleton INSTANCE = new Singleton();
              }
          
              public static Singleton getInstance() {
                  return Holder.INSTANCE;
              }
          }
        • 첫 번째 생성자 호출 때 instanceCreated를 true로 설정 →
          이후 리플렉션이 다시 생성자를 호출하면 RuntimeException 발생.
        • 하지만, 이 필드(instanceCreated) 자체도 리플렉션으로 조작이 가능해서 완벽 방어는 아님
    2. 직렬화(Serialization) 취약
      • ObjectInputStream.readObject()를 쓰면 새 인스턴스가 만들어질 수 있습니다.
      • 방어하려면 readResolve()를 구현해야 합니다
      • private Object readResolve() { return getInstance(); }
    3. 클래스 로딩 시점 제어 불가
      • Lazy하다고 해도 결국 “처음 접근할 때 클래스 로딩”이 트리거인데,
        ClassLoader 동작에 따라 예기치 않게 일찍 로딩될 수 있습니다. (대부분 문제는 아니지만, ClassLoader 복잡한 환경에선 고려 필요)

 

 

'Java > DesignPattern' 카테고리의 다른 글

팩토리 메서드 패턴 vs 추상 팩토리 패턴  (0) 2025.09.07
템플릿 메소드 패턴  (0) 2024.06.03
팩토리 메서드 패턴  (0) 2024.05.16
MVVM 패턴  (0) 2023.07.27