Featured image of post [Design Patterns] 싱글톤 패턴 실습 - 올바른 구현과 현대적 대안

[Design Patterns] 싱글톤 패턴 실습 - 올바른 구현과 현대적 대안

다양한 Singleton 구현 방식을 실제로 구현해보고 멀티스레드 환경에서의 안전성을 확보하는 실습입니다. Thread-safe 구현부터 현대적 DI Container 활용까지, Singleton의 문제점과 대안을 체험하며 실무에서 올바른 설계 방향을 학습합니다.

이 실습에서는 Singleton 패턴의 다양한 구현 방식과 멀티스레드 안전성, 그리고 현대적 대안을 직접 구현해봅니다.

실습 목표

  • Singleton 패턴의 다양한 구현 방식 이해
  • 멀티스레드 환경에서의 안전성 확보
  • Singleton의 문제점과 현대적 대안 학습
  • 의존성 주입을 통한 Singleton 대체

실습 1: 다양한 Singleton 구현

코드 템플릿

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// TODO 1: Eager Initialization
public class EagerSingleton {
    // TODO: 클래스 로딩 시점에 인스턴스 생성
}

// TODO 2: Lazy Initialization (Thread-unsafe)
public class LazySingleton {
    // TODO: 첫 번째 호출 시 인스턴스 생성
}

// TODO 3: Thread-safe Singleton
public class ThreadSafeSingleton {
    // TODO: synchronized 키워드 사용
}

// TODO 4: Double-checked Locking
public class DoubleCheckedSingleton {
    // TODO: volatile과 이중 체크로 최적화
}

// TODO 5: Enum Singleton
public enum EnumSingleton {
    // TODO: Enum을 활용한 최적 구현
}

// TODO 6: Inner Class Holder
public class HolderSingleton {
    // TODO: 내부 클래스를 활용한 지연 로딩
}

실습 2: 데이터베이스 연결 관리자

요구사항

  • 전역적으로 하나의 DB 연결 풀만 존재
  • 설정 정보 중앙 관리
  • 스레드 안전성 보장

코드 템플릿

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public class DatabaseManager {
    private static volatile DatabaseManager instance;
    private final ConnectionPool connectionPool;
    
    // TODO: 1. Double-checked locking 구현
    // TODO: 2. 설정 파일에서 DB 설정 로드
    // TODO: 3. 연결 풀 초기화
    // TODO: 4. Connection 반환 메서드 구현
}

// TODO: 성능 테스트 코드 작성
public class SingletonPerformanceTest {
    @Test
    public void comparePerformance() {
        // TODO: 다양한 구현 방식의 성능 비교
    }
}

실습 3: 현대적 대안 구현

코드 템플릿

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// TODO 1: Spring Bean으로 Singleton 관리
@Configuration
public class SingletonConfig {
    @Bean
    @Scope("singleton")  // 기본값이지만 명시적 표현
    public DatabaseManager databaseManager() {
        // TODO: Spring이 관리하는 Singleton 구현
        return null;
    }
}

// TODO 2: 의존성 주입을 통한 테스트 가능한 설계
@Service
public class UserService {
    private final DatabaseManager databaseManager;
    
    // TODO: 생성자 주입으로 의존성 관리
}

체크리스트

기본 구현

  • 6가지 Singleton 구현 방식 완성
  • 각 방식의 장단점 분석
  • 멀티스레드 테스트 통과
  • 메모리 누수 검증

현대적 대안

  • DI Container 활용 구현
  • 테스트 가능한 설계로 변경
  • Mock 객체 주입 테스트
  • Configuration 외부화

추가 도전

  1. 성능 벤치마크: 각 구현 방식의 성능 측정
  2. 직렬화 문제: Serializable Singleton 구현
  3. 리플렉션 공격: 리플렉션 방어 메커니즘
  4. 클래스로더 문제: 다중 클래스로더 환경 대응

실무 적용

안티패턴 회피

  • Global State 남용 방지
  • 단위 테스트 어려움 해결
  • 결합도 증가 문제 인식

현대적 접근

  • 의존성 주입 프레임워크 활용
  • 설정 외부화
  • 모니터링과 로깅 강화

핵심 포인트: Singleton은 강력하지만 위험한 패턴입니다. 현대적 개발에서는 DI Container를 통한 생명주기 관리가 더 안전하고 유연한 대안입니다.