싱글톤 패턴(2)

김세빈·2025년 4월 22일

CS

목록 보기
11/22

🔒 싱글톤 패턴 구현 7가지 방법

자바에서 싱글톤(Singleton) 패턴은 인스턴스를 단 하나만 생성하여 여러 곳에서 공유해야 할 때 유용하게 사용됩니다. 특히 설정 정보, 로깅, 쓰레드 풀, 캐시 등 전역적으로 하나의 인스턴스를 유지하는 상황에서 자주 사용됩니다.

하지만 멀티스레드 환경에서 안전하면서도 효율적인 싱글톤 패턴을 구현하는 것은 생각보다 쉽지 않습니다. 이번 글에서는 싱글톤 패턴을 구현하는 7가지 대표적인 방법을 정리해보았습니다.


1. 기본 싱글톤 (Thread-Unsafe)

class Singleton {
  private static Singleton instance;

  private Singleton() {
  }

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

이 방식은 간단하고 직관적이지만, 멀티스레드 환경에서는 두 개 이상의 스레드가 동시에 getInstance()를 호출하면 서로 다른 인스턴스를 생성할 수 있는 문제가 있습니다. 따라서 실무에서는 거의 사용되지 않습니다.


2. Synchronized 메서드 (Thread-Safe, 성능 저하)

class Singleton {
  private static Singleton instance;

  private Singleton() {
  }

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

synchronized 키워드를 메서드에 붙이면, 하나의 스레드만 접근할 수 있게 되므로 스레드 안전성이 보장됩니다. 하지만 인스턴스가 이미 생성된 이후에도 모든 getInstance() 호출에 대해 락을 걸게 되어 성능 저하가 발생합니다.


3. 정적 멤버 방식 (Eager Initialization)

class Singleton {
  private static final Singleton instance = new Singleton();

  private Singleton() {
  }

  public static Singleton getInstance() {
    return instance;
  }
}

클래스가 로딩될 때 싱글톤 인스턴스를 바로 생성하는 방식입니다. 스레드에 안전하고 구현이 간단하다는 장점이 있지만, 인스턴스를 사용하지 않더라도 무조건 생성된다는 점에서 리소스 낭비가 생길 수 있습니다.


4. 정적 블록 방식 (Eager Initialization with static block)

class Singleton {
  private static final Singleton instance;

  static {
    instance = new Singleton();
  }

  private Singleton() {
  }

  public static Singleton getInstance() {
    return instance;
  }
}

정적 초기화 블록을 사용하여 예외 처리가 필요한 경우 활용할 수 있는 방식입니다. 마찬가지로 클래스 로딩 시점에 인스턴스가 생성되므로, 필요하지 않아도 생성되는 단점이 있습니다.


5. Lazy Holder (Initialization-on-demand holder idiom)

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

  private Singleton() {
  }

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

내부 정적 클래스를 활용한 방식으로, 가장 많이 추천되는 방식입니다. getInstance()가 호출될 때 내부 클래스가 로딩되면서 인스턴스를 생성하므로 Lazy Initialization이 보장됩니다. 또한 클래스 로딩 시점에서 JVM이 보장하는 스레드 안전성을 그대로 누릴 수 있습니다.


6. Double-Checked Locking (DCL, 이중 검사 락)

public class Singleton {
  private static volatile Singleton instance;

  private Singleton() {
  }

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

인스턴스를 두 번 검사하여 락을 최소화하는 방식입니다. 처음 검사에서는 락을 걸지 않고, 실제 생성이 필요한 경우에만 synchronized 블록을 실행합니다. 성능과 안전성을 모두 잡은 방식이지만 volatile 키워드를 반드시 사용해야 하며, 비교적 복잡한 구조를 갖고 있습니다.


7. Enum Singleton (가장 간단하면서 안전)

public enum Singleton {
  INSTANCE;

  public void doSomething() {
    // 작업 수행
  }
}

자바의 enum은 JVM에서 단 하나만 생성되도록 보장되므로, 싱글톤 패턴에 적합합니다. 직렬화에도 안전하며 리플렉션 공격도 방지할 수 있는 가장 간단하고 강력한 싱글톤 구현 방식입니다. 단, enum을 싫어하거나 다른 언어와 호환이 필요한 경우에는 제약이 될 수 있습니다.

0개의 댓글