싱글턴 패턴

Bo Ram·2025년 3월 18일

1. 싱글턴 패턴이란?

싱글턴(Singleton) 패턴은 하나의 클래스에서 단 하나의 인스턴스만 생성되도록 보장하는 디자인 패턴이다.
이 패턴을 사용하면 프로그램 전역에서 동일한 객체를 공유할 수 있다.

2. 싱글턴 패턴의 특징

  • 하나의 인스턴스만 유지: 메모리 낭비를 줄이고, 전역적으로 같은 객체를 사용하도록 강제한다.
  • 생성자(private) 제한: 외부에서 직접 인스턴스를 생성할 수 없도록 막는다.
  • 전역 접근 가능: 특정 클래스의 인스턴스를 전역적으로 접근할 수 있다.

싱글턴 패턴의 다양한 구현 방식

1. 게으른 초기화(Lazy Initialization)

필요할 때 인스턴스를 생성하는 방식이다.

public class Singleton {
    private static Singleton instance;

    private Singleton() {}

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

문제점: 멀티스레드 환경에서는 여러 개의 인스턴스가 생성될 가능성이 있다.

2. 동기화(synchronized)를 이용한 싱글턴

멀티스레드 환경에서도 안전하게 인스턴스를 생성하기 위해 synchronized 키워드를 사용한다.

public class Singleton {
    private static Singleton instance;

    private Singleton() {}

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

단점: synchronized를 사용할 경우 성능 저하가 발생할 수 있다.

3. 더블 체크 락킹(Double-Checked Locking)

동기화로 인한 성능 저하를 줄이기 위해 두 번 검사하는 방식이다.

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;
    }
}

특징

  • volatile 키워드를 사용하여 인스턴스가 완전히 초기화되기 전에 다른 스레드에서 접근하는 문제를 방지한다.
  • 성능과 안전성을 모두 확보할 수 있는 방법이다.

4. 이른 초기화(Eager Initialization)

클래스가 로드될 때 미리 인스턴스를 생성하는 방식이다.

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

    private Singleton() {}

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

특징

  • 멀티스레드 환경에서도 안전하지만, 필요하지 않아도 인스턴스를 미리 생성하는 단점이 있다.

5. 정적 내부 클래스(Static Inner Class)

클래스가 로드될 때 인스턴스를 생성하지 않고, 필요할 때 생성할 수 있도록 하는 방식이다.

public class Singleton {
    private Singleton() {}

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

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

장점

  • 클래스가 로드될 때 인스턴스를 생성하지 않아 메모리 낭비가 없다.
  • 멀티스레드 환경에서도 안전하다.

6. Enum을 활용한 싱글턴

가장 안전한 방법으로, Java의 Enum을 활용하여 싱글턴을 구현하는 방식이다.

public enum Singleton {
    INSTANCE;

    public void doSomething() {
        System.out.println("싱글턴 객체 사용 중...");
    }
}

특징

  • Enum은 Java에서 자체적으로 싱글턴을 보장하므로 멀티스레드 환경에서도 안전하다.
  • 직렬화(Serialization)와 리플렉션(Reflection) 공격에도 안전하다.

싱글턴 패턴을 사용할 때 고려해야 할 점

1. 리플렉션을 통한 싱글턴 깨짐

Java의 리플렉션(Reflection)을 사용하면 private 생성자라도 강제로 호출하여 새로운 인스턴스를 생성할 수 있다.

import java.lang.reflect.Constructor;

public class SingletonBreak {
    public static void main(String[] args) throws Exception {
        Singleton instance1 = Singleton.getInstance();

        Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        Singleton instance2 = constructor.newInstance();

        System.out.println(instance1 == instance2); // false → 싱글턴 깨짐
    }
}

이 문제를 방지하려면 생성자에서 이미 인스턴스가 존재할 경우 예외를 던지는 방식을 사용해야 한다.

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

    private Singleton() {
        if (INSTANCE != null) {
            throw new RuntimeException("싱글턴 인스턴스는 이미 존재합니다!");
        }
    }

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

이렇게 하면 리플렉션을 사용해 새로운 인스턴스를 만들려고 하면 예외가 발생한다.

2. 직렬화(Serialization) 문제

싱글턴 객체를 직렬화하면 새로운 객체가 생성될 수 있다. 이를 방지하려면 readResolve() 메서드를 구현해야 한다.

protected Object readResolve() {
    return INSTANCE;
}

최신 싱글턴 패턴의 활용

  1. Spring Framework

    • Spring에서는 기본적으로 Bean을 싱글턴으로 관리하므로, 별도로 싱글턴을 구현할 필요가 없다.
    • @Component, @Service, @Repository 같은 애노테이션을 사용하면 자동으로 싱글턴이 적용된다.
  2. 데이터베이스 커넥션 풀

    • DB 커넥션 풀을 관리할 때 하나의 객체를 여러 곳에서 공유해야 하므로 싱글턴 패턴을 활용한다.
  3. 캐싱 시스템

    • 자주 사용되는 데이터를 저장하고 공유하는 캐싱 시스템에서도 싱글턴이 사용된다.
  4. 로그 관리

    • 애플리케이션 전반에서 동일한 로그 객체를 공유하도록 할 때 싱글턴이 유용하다.

결론

싱글턴 패턴은 특정 객체가 단 하나만 존재해야 하는 경우 유용하게 사용될 수 있다.
하지만 잘못 사용하면 테스트가 어려워지고, 의존성 주입(DI)과 충돌할 수도 있다.

  • 멀티스레드 환경에서는 정적 내부 클래스 방식이나 Enum 싱글턴이 가장 안전한 선택이다.
  • Spring 환경에서는 직접 싱글턴을 구현하기보다 Spring의 Bean 관리 기능을 활용하는 것이 더 좋다.
  • 리플렉션 및 직렬화 문제를 고려하여 보안성을 확보해야 한다.
profile
사부작ㅤ사부작

0개의 댓글