PLOD

[Java] 싱글톤 패턴(Singleton pattern) 본문

개발 공부/Java

[Java] 싱글톤 패턴(Singleton pattern)

훌룽이 2023. 12. 31. 23:26

싱글톤 패턴은 객체의 인스턴스를 한 번만 생성하고 생성한 인스턴스를 재사용하는 패턴이다.

singleton pattern

싱글톤 패턴은 하나의 클래스에 오직 하나의 인스턴스만 가지는 패턴이다. 하나의 클래스를 기반으로 여러개의 개별적인 인스턴스를 만들 수 있지만 그렇게 하지 않고 하나의 클래스를 기반으로 단 하나의 인스턴스를 만들어 이를 기반으로 로직을 만드는 데 쓰이며 보통 데이터베이스 연결 모듈에 사용한다. 

싱글톤 패턴의 장점은 하나의 인스턴스를 기반으로 해당 인스턴스를 다른 모듈들이 공유하여 사용하기 때문에 인스턴스를 생성 할 때 드는 비용이 줄어든다.  그렇기 때문에 인스턴스 생성에 많은 비용이 드는 I/O 바운드 작업에 많이 사용한다.

단점은 의존성이 높아지며 TDD(Test Driven Development)를 할 때 걸림돌이 된다. 싱글톤 패턴은 미리 생성된 하나의 인스턴스를 기반으로 구현하는 패턴이므로 각 테스트 마다 독립적인 인스턴스를 만들기가 어렵다. 

dependency Injection

싱글톤 패턴은 사용하기가 쉽고 굉장히 실용적이지만 모듈간의 결합을 강하게 만들 수 있다는 단점이 있다. 이 때 의존성 주입(DI)을 통해 모듈 간의 결합을 조금 더 느슨하게 만들어 해결 할 수 있다. 

앞의 그림처럼 메인 모듈이 직접 다른 하위 모듈에 대한 의존성을 주기보다는 중간에 의존성 주입자가 이 부분을 가로 채 메인 모듈이 간접적으로  의존성을 주입하는 방식이다. 이를 통해 메인 모듈은 하위 모듈에 대한 의존성이 떨어지게 된다. 참고로 이를 디커플링이 된다고 한다.

의존성 주입은 모듈들을 쉽게 교체할 수 있는 구조가 되어 테스팅하기 쉽고 마이그레이션 하기도 수월하다. 또한 구현 할 때 추상화 레이어를 넣고 이를 기반으로 구현체를 넣어주기 때문에 애플리케이션 의존성 방향이 일관되고, 애플리케이션을 쉽게 추론할 수 있으며, 모듈 간의 관계들이 조금 명확해진다. 

하지만 모듈들이 더 분리되므로 클래스의 수가 늘어나 복잡성이 증가될 수 있으며 약간의 런타임 패널티가 생기기도 한다. 

의존성 주입의 원칙은 다음과 같다.

1. 상위 모듈은 하위 모듈에서 어떠한 것도 가져오지 말아야 한다. 
2. 둘 다 추상화에 의존해야 한다. 이 때, 추상화는 세부 사항에 의존하지 말아야 한다. 

싱글톤 패턴 구현 방법

1. 단순한 메서드 호출

싱글톤 패턴 생성 여부를 확인하고 싱글톤이 없으면 새로 만들고 만들어진 인스턴스를 반환한다. 이 방법은 아래의 코드와 같이 여러 개의 쓰레드가 이 객체를 공유할 때 문제가 발생한다. 

public class Singleton {
    private static Singleton INSTANCE;
    private Singleton(){};
    public static Singleton getInstance() {
        if (INSTANCE == null) {
            return INSTANCE = new Singleton();
        }
        return INSTANCE;
    }

2. synchronized

인스턴스를 반환하기 전까지 격리 공간에 놓기 위해 synchronized 키워드로 잠금을 할 수 있다. 최초로 접근한 스레드가 해당 메서드 호출시에 다른 스레드가 접근하지 못하도록 lock을 걸어 준다.

3. static 멤버

정적 멤버나 블록은 런타임이 아니라 최초에 JVM이 클래스 로딩 때 모든 클래스들을 로드할 때 미리 인스턴스를 생성하는데 이를 이용한 방법이다. 클래스 로딩과 동시에 싱글톤 인스턴스를 만든다. 그렇기 때문에 모듈들이 싱글톤 인스턴스를 요청할 때 그냥 만들어진 인스턴스를 반환하면 된다. 이는 편리한 방법 같지만 불필요한 자원 낭비라는 문제점이 있다. 싱글톤 인스턴스가 필요없는 경우도 무조건 싱글톤 클래스를 호출해 인스턴스를 만들어야 하기 때문이다. 

public class Singleton {
    private static final Singleton INSTANCE = new Singleton();
    private Singleton(){};
    public static Singleton getInstance() {
        return INSTANCE;
    }

 

4. Lazy holder

singleInstanceHolder라는 내부클래스를 하나 더 만듬으로써, Singleton클래스가 최초에 로딩되더라도 함께 초기화가 되지 않고 getInstance()가 호출될 때 singleInstanceHolder 클래스가 로딩되어 인스턴스를 생성하게 된다. 

public class Singleton {

    private Singleton(){};

    public static Singleton getInstance() {
        return LazyHolder.INSTANCE;
    }

    private static class LazyHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

5. Double Checked Locking(DCL)

이중확인잠금(DCL)은 인스턴스 생성 여부를 싱글톤 패턴 잠금 전에 한번, 객체를 생성하기 전에 한번 2번 체크하면 인스턴스가 존재하지 않은 때만 잠금을 걸 수 있기 때문에 앞서 생겼던 문제점을 해결 할 수 있다.

6. enum

enum의 인스턴스는 기본적으로 thread safe한 점이 보장되기 때문에 이를 통해 생성 할 수 있다.

public enum EnumSingleton {
    INSTANCE("hello");

    private final String text;

    EnumSingleton(String text){
        this.text = text;
    }

    public void hello(){
        System.out.println(" world");
    }
}

 

Comments