Java

Singleton 패턴

Jay-JI 2024. 7. 16. 19:45

Singleton 패턴이란

 

싱글턴 패턴은 객체의 인스턴스가 오로지 한 개만 생성되도록 설계하는 디자인 패턴을 의미한다. 이는 전역적으로 접근할 수 있는 하나의 객체를 제공하는데 유용하다.

 

이러한 싱글턴 패턴은 다음과 같은 특징을 갖는다.

 

1. 유일성

  • 싱글턴 클래스는 단 하나의 인스턴스만 생성함
  • 여러 번 인스턴스화를 시도해도 항상 같은 인스턴스를 반환함

2. 전역 접근

  • 싱글턴 객체는 전역적으로 접근할 수 있는 방법을 제공한다.
  • 이를 통해 어플리케이션의 다른 부분에서 언제든지 동일한 인스턴스에 접근할 수 있다.

 

Singleton 패턴 구현 방법

 

자바에서 싱글턴 패턴을 구현하는 방법은 여러가지가 있다. 하지만 여기에서는 권장되는 2가지 방법만 제시할 것이다. (나머지 방법들은 단점이 뚜렷하기 때문)

 

1. Bill Pugh Solution(LazyHolder)

  • 멀티스레드 환경에서 안전하고 Lazy Loading(나중에 객체 생성) 도 가능한 완벽한 싱글턴 기법
  • 클래스 안에 내부 클래스(holder)를 두어 JVM의 클래스 로더 매커니즘과 클래스가 로드되는 시점을 이용한 방법 (스레드 세이프함)
  • static 메소드에서는 static 멤버만을 호출할 수 있기 때문에 내부 클래스를 static으로 설정
  • 이밖에도 내부 클래스의 치명적인 문제점인 메모리 누수 문제를 해결하기 위하여 내부 클래스를 static으로 설정
  • 다만 클라이언트가 임의로 싱글턴을 파괴할 수 있다는 단점을 지님 (Reflection API, 직렬화/역직렬화를 통해)
class Singleton {

    private Singleton() {}

    // static 내부 클래스를 이용
    // Holder로 만들어, 클래스가 메모리에 로드되지 않고 getInstance 메서드가 호출되어야 로드됨
    private static class SingleInstanceHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingleInstanceHolder.INSTANCE;
    }
}
  • 우선 내부클래스를 static으로 선언하였기 때문에 싱글턴 클래스가 초기화되어도 SingleInstanceHolder 내부 클래스는 메모리에 로드되지 않음
  • 어떠한 모듈에서 getInstance() 메서드를 호출할 때 SingleInstanceHolder 내부 클래스의 static 멤버를 가져와 리턴하게 되는데, 이때 내부 클래스가 한번만 초기화되면서 싱글턴 객체를 최초로 생성 및 리턴하게 된다.
  • 마지막으로 final 로 지정함으로서 다시 값이 할당되지 않도록 방지

 

2. Enum 이용

  • enum은 애초에 멤버를 만들때 private로 만들고 한번만 초기화 하기 때문에 thread safe함
  • enum 내에서 상수 뿐만 아니라 변수나 메서드를 선언해 사용이 가능하기 때문에 이를 이용해 싱글턴 클래스처럼 응용이 가능
  • 위의 Bill Pugh Solution 기법과 달리 클라이언트에서 Reflection을 통한 공격에도 안전
  • 하지만 만일 싱글턴 클래스를 멀티턴(일반적인 클래스)로 마이그레이션 해야 할 때 처음부터 코드를 다시 짜야 되는 단점이 존재 (개발 스펙은 변경될 수 있기 때문에)
  • 클래스 상속이 필요 할 때,  enum 외의 클래스 상속은 불가능하다.
enum SingletonEnum {
    INSTANCE;

    private final Client dbClient;
	
    SingletonEnum() {
        dbClient = Database.getClient();
    }

    public static SingletonEnum getInstance() {
        return INSTANCE;
    }

    public Client getClient() {
        return dbClient;
    }
}

public class Main {
    public static void main(String[] args) {
        SingletonEnum singleton = SingletonEnum.getInstance();
        singleton.getClient();
    }
}

 

 

이 둘의 사용 선택은 싱글턴 클래스의 목적에 따라 갈린다.

  • Bill Pugh Solution(LazyHolder) : 성능이 중요시 되는 환경
  • Enum : 직렬화, 안정성 중요시 되는 환경

 

Singleton 패턴의 장단점

 

싱글턴 패턴은 고정된 메모리 영역을 가지고 하나의 인스턴스만 사용하기 때문에 메모리 낭비 방지할 수 있으며, DBCP(DataBase Connection Pool)처럼 공통된 객체를 여러 개 생성해야 하는 상황에서 많이 사용된다.

 

하지만 싱글턴 패턴은 다음과 같은 단점도 가지고 있다.

 

1. 모듈간 의존성 높아짐

  • 대부분의 싱글턴을 이용하는 경우 인터페이스가 아닌 클래스의 객체를 미리 생성하고 정적 메소드를 이용해 사용하기 때문에 클래스 사이에 강한 의존성과 높은 결합이 생김
  • 즉, 하나의 싱글턴 클래스를 여러 모듈들이 공유를 하는데 만일 싱글톤의 인스턴스가 변경되면 이를 참조하는 모듈들도 수정이 필요하게 된다.

2. SOLID 원칙 위배

  • 싱글턴 인스턴스 자체가 하나만 생성되기 때문에 여러가지 책임을 지니게 되는 경우가 많아 단일 책임 원칙(SRP)를 위반하는 경우가 많음
  • 싱글턴 인스턴스가 혼자 너무 많은 일을 하거나, 많은 데이터를 공유하면 다른 클래스들 간의 결합도가 높아지게 되어 개방-폐쇄 원칙(OCP)에도 위배됨
  • 의존 관계상 클라이언트가 인터페이스와 같은 추상화가 아닌, 구체 클래스에 의존하게 되어 의존 역전 원칙(DIP)에도 위배됨
  • 이러한 이유로 싱글턴 패턴을 객제 지향 프로그래밍의 안티 패턴이라고도 함

3. 테스트 어려움

  • 단위 테스트는 테스트가 서로 독립적이어야 하며 테스트를 어떤 순서로든 실행 할 수 있어야 한다.
  • 싱글턴 인스턴스는 자원을 공유하고 있기 때문에 테스트가 결함없이 수행되려면 매번 인스턴스의 상태를 초기화 시켜야 함

4. 멀티스레드 환경 문제

  • 멀티스레드 환경에서 싱글턴 클래스를 적절히 구현하지 않으면 인스턴스가 여러 개 생성될 수도 있다.

 

Singleton 클래스와 정적 클래스의 차이

 

자바에서 정적 클래스란 정적 메소드(static method)만 가지고 있는 클래스를 의미한다. 참고로static method는 클래스 초기화 시에 JVM의 method area에 저장되며 프로그램 종료 시 제거된다.

그런데 정적 클래스 또한 싱글턴 객체처럼 전역으로 접근 가능한 클래스라 비슷한 상황에서 싱글턴 객체를 사용할지 정적 클래스를 사용할지 고민될 수 있다.

 

아래는 둘의 특징을 비교한 표이다.

항목 싱글턴 패턴 정적 클래스
특징 1. 상속받아서 사용 가능
2. 메소드 파라미터로 사용 가능
1. 객체 생성을 하지 않는다.(효율성)
권장 환경 1. 완벽한 객체지향을 필요로 할 때
2. 레이지 로딩이 필요할 때
1. 유틸 메소드를 보관하는 용도로 사용할 때
2. 다형성이나 상속이 필요 없는 클래스
사용 예시 1. DB 연결 관리 
2. 설정 관리
1. 유틸리티 함수(ex. Math.MAX())
2. 상수 관리

 

  • LazyLoading(레이지 로딩) : 객체의 초기화를 실제로 필요할 때까지 미루는 방법

출처 :
https://www.youtube.com/watch?v=5oUdqn7WeP0
https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%EC%8B%B1%EA%B8%80%ED%86%A4Singleton-%ED%8C%A8%ED%84%B4-%EA%BC%BC%EA%BC%BC%ED%95%98%EA%B2%8C-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90