Singleton 디자인 패턴: 개발자를 위한 완전 가이드

발행: (2026년 2월 6일 오후 08:17 GMT+9)
10 min read
원문: Dev.to

I’m happy to translate the article for you, but I’ll need the full text of the post (the content you’d like translated). Could you please paste the article’s body here? Once I have the text, I’ll keep the source line exactly as you provided and translate the rest into Korean while preserving all formatting, markdown, and technical terms.

Introduction

우리는 모두 그런 경험을 해봤습니다. 프로젝트를 진행 중인데 갑자기 애플리케이션 전체에서 정확히 하나의 인스턴스가 필요하다는 것을 깨닫게 됩니다. 전형적인 예로는 데이터베이스 연결 풀, 설정 관리자, 로깅 서비스 등이 있습니다. 여러 인스턴스를 만들면 낭비가 되거나 혼란을 초래하거나 심지어 위험할 수도 있습니다.

이때 Singleton 패턴이 구원군이 됩니다.

싱글톤 패턴이란?

클래스의 인스턴스를 단일 객체로 제한하고 해당 객체에 대한 전역 접근점을 제공하는 디자인 패턴입니다.

기본 구현

public class DatabaseConnection {
    // Static variable to hold the single instance
    private static DatabaseConnection instance;

    // Private constructor prevents instantiation from other classes
    private DatabaseConnection() {
        System.out.println("Database connection established");
    }

    // Public method to provide access to the instance
    public static DatabaseConnection getInstance() {
        if (instance == null) {
            instance = new DatabaseConnection();
        }
        return instance;
    }

    public void executeQuery(String query) {
        System.out.println("Executing: " + query);
    }
}

사용 예시

public class Application {
    public static void main(String[] args) {
        DatabaseConnection db1 = DatabaseConnection.getInstance();
        DatabaseConnection db2 = DatabaseConnection.getInstance();

        System.out.println(db1 == db2); // Output: true (same instance)
    }
}

문제: 이 기본 버전은 스레드‑안전하지 않습니다. 멀티스레드 환경에서 두 스레드가 getInstance()를 동시에 호출하면 별개의 인스턴스를 생성할 수 있습니다.

스레드‑안전 싱글톤 (즉시 초기화)

public class ConfigurationManager {
    // Instance created at class loading time
    private static final ConfigurationManager instance = new ConfigurationManager();

    private ConfigurationManager() {
        // Load configuration from file
        System.out.println("Loading configuration...");
    }

    public static ConfigurationManager getInstance() {
        return instance;
    }

    public String getProperty(String key) {
        // Return configuration property
        return "value_for_" + key;
    }
}

장점: 스레드‑안전합니다. 인스턴스가 클래스 로드 시에 생성되기 때문입니다.
단점: 지연 초기화가 없습니다 – 인스턴스가 사용되지 않더라도 생성됩니다.

스레드‑안전 싱글톤 (지연 초기화와 이중 검사 잠금)

public class Logger {
    private static volatile Logger instance;

    private Logger() {
        System.out.println("Logger initialized");
    }

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

    public void log(String message) {
        System.out.println("[LOG] " + message);
    }
}

핵심 포인트

  • volatile은 스레드 간 변경 사항의 가시성을 보장합니다.
  • 이중 검사 잠금은 동기화 오버헤드를 최소화합니다.

Bill Pugh 싱글톤 (추천 접근법)

public class CacheManager {

    private CacheManager() {
        System.out.println("Cache Manager initialized");
    }

    // Static inner class – not loaded until referenced
    private static class SingletonHelper {
        private static final CacheManager INSTANCE = new CacheManager();
    }

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

    public void put(String key, Object value) {
        System.out.println("Caching: " + key);
    }

    public Object get(String key) {
        return "cached_value_for_" + key;
    }
}

왜 선호되는가

  • 스레드‑안전합니다.
  • 지연 초기화를 지원합니다.
  • 명시적인 동기화가 필요하지 않습니다.

Enum Singleton (리플렉션 방지)

public enum ApplicationContext {
    INSTANCE;

    private String environment;

    ApplicationContext() {
        this.environment = "production";
        System.out.println("Application Context initialized");
    }

    public String getEnvironment() {
        return environment;
    }

    public void setEnvironment(String environment) {
        this.environment = environment;
    }
}

사용법

ApplicationContext context = ApplicationContext.INSTANCE;
context.setEnvironment("development");

장점

  • 직렬화와 스레드‑안전성을 자동으로 제공한다.
  • 리플렉션 공격을 방지한다.

Where to Use the Singleton Pattern

  • Configuration Management – 애플리케이션 설정을 한 번 로드하고 전역에서 접근.
  • Logger Classes – 중앙 집중식 로깅 메커니즘.
  • Database Connection Pools – 재사용 가능한 연결 풀 관리.
  • Cache Managers – 인‑메모리 캐싱 시스템.
  • Thread Pools – 작업자 스레드 관리.
  • Device Drivers – 하드웨어 자원에 접근 (예: 프린터).

실제 예제

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

    private Properties properties;

    private AppConfig() {
        properties = new Properties();
        loadConfiguration();
    }

    private void loadConfiguration() {
        // Load from file, environment variables, etc.
        properties.setProperty("app.name", "MyApplication");
        properties.setProperty("max.connections", "100");
        properties.setProperty("timeout", "30000");
    }

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

    public String get(String key) {
        return properties.getProperty(key);
    }
}

싱글톤 패턴의 장점

  • Controlled Access – 단 하나의 인스턴스만 존재하여 애플리케이션 전반에 일관된 상태를 보장합니다.
  • Memory Efficiency – 무거운 객체를 여러 개 생성하는 것을 방지합니다.
  • Global Access Point – 참조를 전달할 필요 없이 어디서든 쉽게 접근할 수 있습니다.
  • Lazy Initialization – (지연 방식을 사용할 경우) 인스턴스가 필요할 때만 생성됩니다.

Cons and Pitfalls

  • Testing Challenges – 싱글톤은 전역 상태를 도입하여 단위 테스트를 어렵게 합니다. 인스턴스를 쉽게 모킹하거나 교체할 수 없습니다.

    // Difficult to test
    public class OrderService {
        public void processOrder(Order order) {
            Logger.getInstance().log("Processing order: " + order.getId());
            // Hard to verify logging behavior in tests
        }
    }
  • Hidden Dependencies – 싱글톤을 사용하는 클래스는 의존성을 명시적으로 선언하지 않아 의존성 역전 원칙을 위반합니다.

  • Violates Single Responsibility Principle – 클래스가 핵심 기능과 인스턴스 생성 두 가지를 모두 관리합니다.

  • Global State – 전역 상태는 결합도를 높이고 코드를 이해하기 어렵게 만들 수 있습니다.

  • Concurrency Issues – 올바르게 구현되지 않으면 레이스 컨디션과 같은 동시성 문제가 발생할 수 있습니다.

  • Serialization Problems – 역직렬화 시 여러 인스턴스가 생성되지 않도록 특별한 처리가 필요합니다.

싱글톤을 피해야 할 때

  • 앞으로 여러 인스턴스가 필요할 수 있습니다 (지금은 하나만 필요하다고 생각하더라도).
  • 클래스에 가변 상태가 있어 애플리케이션의 여러 부분에서 수정합니다.
  • 테스트 가능한 코드를 작성하고 있으며 의존성을 주입해야 합니다.
  • 객체가 가볍고 여러 인스턴스를 생성해도 문제가 없습니다.
  • ‘하나의 인스턴스’가 모호한 분산 시스템에서 작업하고 있습니다.

더 나은 대안

  • Dependency Injection frameworks – Spring, Guice 등.
  • Factory patterns 인스턴스 관리와 함께.
  • Static utility classes (무상태 연산을 위한).

Source:

올바른 상황을 인식하는 방법

다음 질문들을 스스로에게 물어보세요:

  1. 정말 정확히 하나의 인스턴스가 필요합니까?
    솔직하게 생각하세요. “애플리케이션당 하나의 인스턴스”는 “하나면 충분하다고 생각한다”와는 다릅니다.

  2. 이 자원이 반드시 공유되어야 합니까?
    데이터베이스 연결 풀: 예.
    사용자 세션: 아니오.

  3. 이 전역 상태가 문제를 일으킬 가능성이 있습니까?
    애플리케이션의 서로 다른 부분이 서로 다른 설정을 필요로 한다면, 싱글톤은 답이 아닙니다.

  4. 이를 쉽게 테스트할 수 있습니까?
    싱글톤 때문에 테스트가 어려워진다면, 대신 의존성 주입을 고려하세요.

  5. 이것이 무상태 유틸리티입니까?
    그렇다면 싱글톤이 필요 없을 수도 있습니다—정적 메서드만 사용하면 됩니다.

최종 생각

핵심은 여러분의 구체적인 사용 사례를 이해하는 것입니다. 이것이 진정으로 공유되는 리소스인가요? 하나의 인스턴스를 갖는 것이 애플리케이션에 실제로 도움이 될까요? 효과적으로 테스트할 수 있나요?

Remember, design patterns are guidelines, not rules. The best code solves your problem clearly, maintainably, and efficiently. Sometimes that’s a Singleton; often, it isn’t.

Happy coding!

Back to Blog

관련 글

더 보기 »

Java 기능::

Java 프로그래밍 언어는 처음에 임베디드 시스템, 셋톱 박스, 그리고 텔레비전에서 작동하도록 개발되었습니다. 요구 사항에 따라, 다양한 플랫폼에서 실행되도록 설계되었습니다.

Java의 특징

Java 기능에 대한 표지 이미지 https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.am...