객체 생성 패턴 : 싱글톤 패턴

Handwoong
Handwoong
Published in
6 min readDec 3, 2023
Photo by Patrick Tomasso on Unsplash

싱글톤 패턴이란?

객체의 인스턴스를 오직 하나만 생성하고 제공하는 디자인 패턴입니다.
싱글톤 패턴은 인스턴스가 하나만 존재해야 하는 경우 사용합니다.

예를들어 시스템 환경 설정의 인스턴스가 여러개라면 문제가 생길 수 있습니다.

  1. 사용자가 배경화면 색을 빨간색으로 설정했습니다.
  2. 프로그램은 Setting 클래스의 인스턴스를 생성하여 배경화면 색을 빨간색으로 변경합니다.
  3. 변경한 설정을 적용하려는데 배경화면 색을 설정하지 않은 엉뚱한 settingB 인스턴스로 설정을 초기화 했습니다.

위와 같은 상황은 인스턴스가 여러개 생성되어 생길 수 있는 문제입니다.
싱글톤 패턴을 사용하여 인스턴스를 단 하나만 생성하고 제공함으로써 이러한 문제를 해결할 수 있습니다.

싱글톤 패턴 구현

우선 외부에서 Setting 클래스의 인스턴스를 생성하지 못하도록 방지해야 합니다.
새로운 인스턴스의 생성을 방지하려면 private 생성자를 이용할 수 있습니다.

다음으로 Setting 내부에서 인스턴스를 생성하고 외부로 제공해주어야 합니다.

처음 예제에 구현한 싱글톤 패턴을 적용합니다.

  1. settingA와 settingB는 동일한 인스턴스를 참조하고 있습니다.
  2. settingA의 backgroundColor를 빨간색으로 변경합니다.
  3. 동일한 인스턴스를 참조하고 있기 때문에 settingA의 backgroundColor와 settingB의 backgroundColor는 동일합니다.

문제점

최초 instance가 null일 때 멀티 쓰레드 환경에서 동시적으로 getInstance를 호출한다면 어떻게 될까요?
하나의 인스턴스만 생성되는 것이 아니라 여러 인스턴스가 생성될 수 있습니다.

문제를 재현하기 위해 getInstance의 코드를 조금 수정하겠습니다.

Application에서 쓰레드를 생성하고 Setting인스턴스를 출력해보겠습니다.

결과:

쓰레드로부터 안전하게 싱글톤 패턴 구현

synchronized 키워드

synchronized 키워드를 사용해서 getInstance메서드에 하나의 쓰레드만 접근할 수 있도록 만들 수 있습니다.

synchronized 키워드를 사용하면 동기화 작업(락)이 진행됩니다.
이 동기화 작업으로 인해 약간의 성능 손해가 발생할 수 있습니다.

eager initialization

인스턴스를 사용 시점에 생성하는 것이 아니라 미리 생성해둘 수 있습니다.

미리 인스턴스를 생성한다는 것이 단점이 될 수 있습니다.
인스턴스의 생성 비용이 비싸서 많은 리소스를 들여 생성하였지만, 애플리케이션에서 사용하지 않는 상황이 있을 수 있습니다.

double checked locking

getInstance 메서드에 synchronized 키워드를 사용하는 것이 아닌 메서드 내부에서 synchronized 키워드를 사용하는 방법입니다.

기존 메서드에 synchronized 키워드를 사용한 방법과의 차이점으로 인스턴스가 존재하는 경우 동기화 작업(락)이 진행되지 않습니다.

  1. A쓰레드가 1차로 instance가 존재하는지 검사합니다. => 존재하지 않음
  2. B쓰레드 또한 1차로 instance가 존재하는지 검사합니다. => 존재하지 않음
  3. A쓰레드는 2차로 instance가 존재하는지 검사합니다. => 존재하지 않음
  4. B쓰레드는 2차 검증을 하려해도 synchronized로 인해 동기화 작업(락)이 진행되어 작업이 끝나길 기다립니다.
  5. A쓰레드는 인스턴스를 생성합니다.
  6. 동기화 작업이 끝난 후 B쓰레드는 2차로 instance가 존재하는지 검사합니다. => 존재함

이 방법은 volatile 키워드 부터 시작해서 조금 복잡할 수 있다라는 단점이 있습니다.

Bill Pugh Solution

static inner class를 사용한 방법입니다.

getInstance를 호출하는 시점에 내부 클래스가 로딩되어 생성됨으로 멀티 쓰레드 환경에서도 안전하고, Lazy Loading 또한 가능합니다.
double checked locking 방법보다 복잡하지 않고 간단하다는 장점이 있습니다.
싱글톤을 구현할 때 제일 권장되는 방법입니다.

싱글톤 패턴을 깨트리는 방법

싱글톤 패턴은 단 하나의 인스턴스만 생성해야 합니다.
하지만 리플렉션직렬화 & 역직렬화를 통해 추가적으로 인스턴스 생성이 가능합니다.

리플렉션

리플렉션을 사용해서 private생성자 접근을 허용시켜 새 인스턴스를 생성할 수 있습니다.

결과:

리플렉션을 통한 인스턴스의 생성은 막을 수 있는 방법이 없습니다.

직렬화 & 역직렬화

Setting 클래스가 직렬화 가능하도록 Serializable 인터페이스를 구현합니다.

Setting 클래스를 직렬화하고, 역직렬화 하게되면 새로운 인스턴스가 생성됩니다.

결과:

명시적으로 오버라이드가 가능하진 않지만 readResolve라는 메서드를 구현하면 역직렬화 시 해당 메서드가 사용됩니다.

readResolve 메서드가 싱글톤으로 구현한 인스턴스를 반환하도록 구현합니다.
그런다음 직렬화 & 역직렬화를 통해 결과를 확인해보면 동일한 인스턴스를 반환하는 것을 확인할 수 있습니다.

결과:

Enum으로 싱글톤 패턴 구현

Enum으로 싱글톤 패턴을 구현하게 되면 리플렉션직렬화 & 역직렬화방법으로도 싱글톤 패턴을 깨트릴 수 없습니다.

하지만 Enum의 특성 상 Lazy Loading이 불가능하고 상속 또한 불가능 하다는 단점이 있습니다.

결론

싱글톤 패턴을 구현할 때 Bill Pugh 방법인 static inner class를 사용하거나 enum을 사용하는 것이 좋습니다.
인스턴스 생성 비용이 비싸 Lazy Loading이 필요하거나 상속이 필요하다면 static inner class를, 그 이외의 상황에선 enum을 선택할 수 있습니다.

--

--

Handwoong
Handwoong
0 Followers
Editor for

Backend Developer (Java, Spring)