Design Pattern

[디자인 패턴] 싱글톤 패턴 (Singleton Pattern)

꾸준함. 2024. 6. 13. 14:59

싱글톤 패턴

  • 두 가지 목적을 가진 패턴
    • 인스턴스를 오직 한 개만 생성
    • 만들어진 하나의 인스턴스에 글로벌하게 접근할 수 있는 방법을 제공

 

https://www.geeksforgeeks.org/singleton-design-pattern/

 

싱글톤 패턴 구현 방법

싱글톤 패턴은 여러 가지 방법으로 구현 가능하며 각각의 특징에 대해 간략하게 설명해 보겠습니다.

 

1. 싱글톤 패턴 구현 방법 #1

  • 동기화를 사용해 멀티쓰레드 환경에서 안전하게 만드는 방법

 

 

 

부연 설명

  • 클래스 외부에서 생성자를 호출할 수 없도록 private 생성자를 선언하여 새로운 인스턴스 생성 불가능하도록 처리
  • 싱글톤 패턴 목적에 맞게 클래스 내에서 인스턴스를 만들고 글로벌하게 접근할 수 있도록 처리해야 함
    • 글로벌하게 접근할 수 있어야 하므로 정적(static) 메서드로 선언
    • 멀티 쓰레드 환경에서도 thread-safe 하도록 synchronized 키워드를 getInstance() 메서드에 붙여 클래스락을 획득한 상태에서만 인스턴스를 반환받을 수 있도록 처리
    • 단, 락 획득을 하는 과정에서 성능 저하 유발할 수 있음

 

2. 싱글톤 패턴 구현 방법 #2

  • Eager Initialization을 사용하는 방법

 

 

 

부연 설명

  • 여러 쓰레드가 접근해도 단순히 미리 생성한 인스턴스를 반환하므로 thread-safe
  • 인스턴스 생성 비용이 상대적으로 비싸지 않을 경우 eager initialization 적용
    • 인스턴스 생성 비용이 비쌀 경우 미리 생성한다는 것 그 자체가 단점이 될 수 있음
    • 여기서 생성 비용이 비싸다는 생성 하는데 오래 걸리고 메모리를 많이 사용하는 등 리소스 소모량이 크다는 것

 

3. 싱글톤 패턴 구현 방법 #3

  • double checked locking 기법 적용

 

 

 

부연 설명

  • 자바 1.5 버전부터 동작하는 코드
  • volatile 키워드를 붙여야 하는데 이는 instance가 생성됐는지 확인하는 조건문 자체는 동기화 블록 내 포함되지 않으므로 instance를 항상 캐시가 아닌 메모리로부터 불러와야 하기 때문
  • 1번 방법을 보완한 코드
    • 메서드 자체를 동기화하는 것은 비용이 크므로
    • 인스턴스가 생성되지 않은 시점에 다수의 쓰레드가 동시에 if문을 통과하더라도 동기화 블록에서 한 번 더 체크하므로 thread-safe
    • instance가 null일 때만 synchronized 구문을 접근하므로 1번 방법에 비해 성능 상 우위
    • 인스턴스를 필요로 하는 시점에 만들 수 있다는 것 또한 장점

 

4. 싱글톤 패턴 구현 방법 #4

  • static inner 클래스를 사용하는 방법

 

 

 

부연 설명

  • getInstance가 호출될 때 LazySettingsHolder가 로딩되고 그때 Instance가 만들어져 Lazy Loading 되므로 static final 인스턴스를 선언했는데도 불구하고 지연 초기화 기법

 

5. 싱글톤 패턴 구현 방법 #5

  • enum 클래스를 사용하는 방법

 

 

 

부연 설명

  • 후술 할 "싱글톤 패턴을 깨트리는 방법"으로부터 안전한 클래스로 가장 안정적으로 싱글톤 패턴을 구현할 수 있는 방법
  • 로딩하는 순간 인스턴스 자체가 미리 생성된다는 단점과 상속을 사용하지 못한다는 단점 존재

 

싱글톤 패턴 구현 깨트리는 방법

개발자가 제대로 thread-safe 하게 클래스를 설계하더라도 사용하는 클라이언트 단에서 두 가지 방법으로 깨트릴 수 있습니다.

 

1. 싱글톤 패턴 구현 깨트리는 방법 #1

 

 

 

부연 설명

  • 리플렉션 기능을 이용하면 프로그램에서 임의의 클래스에 접근할 수 있으며 Class 객체가 주어지면 클래스 정보를 통해 생성자 시그니처, 메서드 시그니처, 그리고 필드 타입, 멤버 필드 명 등을 가져올 수 있음
    • 생성자 인스턴스를 통해 객체를 생성할 수 있음
    • 메서드 인스턴스를 통해 메서드를 실행시킬 수 있음

 

  • 싱글톤 패턴을 적용했기 때문에 private 생성자로 선언됐고 싱글톤 패턴을 깨트리기 위해서는 해당 생성자를 접근하고 호출해 새로운 인스턴스를 생성해야 하므로 setAcessible(true);
  • enum 클래스의 경우 리플렉션으로도 싱글톤 패턴이 파훼가 안됨
    • enum 클래스의 생성자를 접근하려고 시도하면 Cannot reflectivley create enum objects 메시지가 뜸

 

 

2. 싱글톤 패턴 구현 깨트리는 방법 #2

  • 직렬화 & 역직렬화를 사용하는 방법
  • 직렬화를 적용하기 위해서는 클래스가 마커 인터페이스인 Serializable 구현체여야 함

 

 

 

부연 설명

  • 이펙티브 자바 아이템 89에서도 언급했다시피 Serializable를 구현하는 순간부터 싱글톤 패턴이 아님
    • 역직렬화할 때 readObject() 메서드가 호출되고 어떤 readObject() 메서드를 선언하든 해당 클래스가 초기화될 때 만들어진 인스턴스와는 별개인 인스턴스를 반환하기 때문
    • 단, readResolve() 메서드를 재정의하면 readObject()가 생성한 인스턴스를 다른 것으로 대체 가능하고 역직렬화한 객체의 클래스가 readResolve() 메서드를 적절히 구현했다면 역직렬화 후 새로 생성된 객체를 인수로 해당 메서드가 호출되고 해당 메서드가 반환한 객체 참조가 새로 생성된 객체를 대신 반환

 

 

 

* enum의 경우 별도 처리 없어도 직렬화/역직렬화로부터 안전함

 

실무에서 쓰이는 싱글톤 패턴 

 

1. 자바의 Runtime

 

 

부연 설명

  • currentRuntime 인스턴스를 미리 생성하고 getRuntime() 정적 메서드를 통해서만 인스턴스를 반환받도록 구현됨
  • 외부에서 인스턴스를 생성할 수 없도록 private 생성자 선언

 

2. 스프링 프레임워크의 싱글톤 스코프 (singleton scope)

  • 기본 빈(scope) 스코프로, 스프링 IoC 컨테이너(ApplicationContext)가 시작될 때 한 번 생성된 빈 인스턴스를 여러 곳에서 공유하도록 설정하며 애플리케이션 전체에서 해당 빈의 단일 인스턴스가 존재하도록 함
    • 컨테이너가 시작될 때, 정의된 빈들이 한 번 생성
    • 각 요청에서 동일한 빈 인스턴스를 반환
    • 애플리케이션 종료 시점까지 해당 빈 인스턴스는 동일하게 유지

 

2.1 스프링 싱글톤 스코프 vs 싱글톤 패턴

 

생성 시점

  • 스프링 싱글톤 스코프: 스프링 컨테이너가 시작될 때 빈이 초기화되고, 이후 모든 요청에서 동일한 빈을 반환
  • 싱글톤 패턴: 클래스의 인스턴스가 처음 요청될 때 생성되며, 이후 동일한 인스턴스를 반환

 

관리 주체

  • 스프링 싱글톤 스코프: 스프링 컨테이너가 빈의 생명주기를 관리하며 스프링 컨테이너가 초기화되고 종료될 때까지 빈의 생명주기를 책임짐
  • 싱글톤 패턴: 클래스 자체가 인스턴스를 관리하며 인스턴스 생성과 반환 로직이 클래스 내에 포함되어 있음

 

주입 방식

  • 스프링 싱글톤 스코프: 의존성 주입(Dependency Injection)을 통해 관리됨
  • 싱글톤 패턴: 클래스 내부에서 직접 인스턴스를 관리하기 때문에, 의존성 관리가 비교적 제한적

 

유연성

  • 스프링 싱글톤 스코프: 스프링 설정 파일(XML, 자바 어노테이션 등)을 통해 쉽게 설정을 변경할 수 있으며 이를 통해 같은 클래스라도 다른 설정으로 여러 인스턴스를 가질 수 있음
  • 싱글톤 패턴: 코드 수정 없이 인스턴스 생성 방식을 변경하기 어려움

 

참고

코딩으로 학습하는 GoF의 디자인 패턴 - 백기선 강사님

반응형