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

sunny·2023년 3월 5일
0

디자인 패턴

목록 보기
2/11
post-thumbnail

싱글톤 패턴, Singleton Pattern


싱글톤 패턴이란?

하나의 클래스에 오직 하나의 인스턴스만 가지는 패턴

  • 보통 데이터베이스 연결 모듈에 많이 사용
  • 인스턴스가 필요할 때, 똑같은 인스턴스를 만들지 않고 기존의 인스턴스를 활용

1_GOAK3XdRvjrcpX9dq0fUrQ.png

img.png

싱글톤 패턴을 사용하는 이유

  • 메모리 낭비 방지
  • 이미 생성된 인스턴스를 활용하기 때문에 속도 측면에 이점이 있다
  • 전역으로 사용하는 인스턴스이기 때문에 다른 여러 클래스에서 데이터를 공유하며 사용할 수 있다

싱글톤 패턴의 단점

TDD (Test Driven Development)에서의 어려움

  • 단위 테스트는 테스트가 서로 독립적이어야 하며 테스트를 어떤 순서로든 실행할 수 있어야 한다.
  • But, 싱글톤 패턴은 미리 생성된 하나의 인스턴스를 기반으로 구현하는 패턴이므로 각 테스트마다 ‘독립적인’ 인스턴스를 만들기 어렵다.

의존성 주입 (Dependency Injection)

싱글톤 패턴은 모듈 간의 결합을 강하게 만들 수 있다는 단점이 있다

👉 이때, 의존성 주입을 통해 모듈 간의 결합을 조금 더 느슨하게 만들어 해결할 수 있다

💡 A가 B에 의존성이 있다 👉 B의 변경 사항에 대해 A 또한 변해야 된다

💡 의존성 주입이란?
메인 모듈이 ‘직접’ 다른 하위 모듈에 의존성을 주지 않고, 의존성 주입자를 중간에 둬서 간접적으로 의존성을 주입하는 방식

Untitled (3)

싱글톤 패턴의 6가지 구현 방법

1. Eager Initialization

싱글톤 클래스의 인스턴스를 클래스 로딩 단계에서 생성하는 방법

public class Singleton {
    
    private static final Singleton instance = new Singleton();
    
    // private constructor to avoid client applications to use constructor
    private Singleton(){}
 
    public static Singleton getInstance(){
        return instance;
    }
}
  • File System, Database Connection 등 큰 리소스들을 다룰 때는 부적합
  • Exception에 대한 Handling 제공하지 않음

2. Static Block Initialization

Eager Initialization과 유사하나, static block을 통해서 Exception Handling에 대한 옵션 제공

public class Singleton {
 
    private static Singleton instance;
    
    private Singleton(){}
    
    //static block initialization for exception handling
    static{
        try{
            instance = new Singleton();
        }catch(Exception e){
            throw new RuntimeException("Exception occured in creating singleton instance");
        }
    }
    
    public static Singleton getInstance(){
        return instance;
    }
}
  • 큰 리소스 다룰 때는 부적합

3. Lazy Initialization

global access 한 getInstance() 메소드를 호출할 때에 인스턴스가 없다면 생성 👉 처음 사용할 때 초기화

public class Singleton {
 
    private static Singleton instance;
    
    private Singleton(){}
    
    public static Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}
  • Eager Initialization과 Static Block Initialization은 해당 인스턴스를 사용하지 않더라도 인스턴스를 생성하기 때문에 낭비가 발생할 수도 있으나, Lazy Initialization은 이러한 낭비에 대한 해결책이 될 수 있음
  • But, multi-thread 환경에서 동기화 문제가 발생할 수 있음 👉 single-thread 환경이 보장됐을 때 사용하기!

4. Thread Safe Singleton

Lazy Initialization의 multi-thread 환경에서 동기화 문제를 해결하기 위한 방법 👉 getInstance() 메소드에 synchronized를 걸어두는 방식

💡 Synchronized
임계 영역 (Critical Section)을 형성해 해당 영역에 오직 하나의 thread만 접근 가능하게 해준다.

public class Singleton {
 
    private static Singleton instance;
    
    private Singleton(){}
    
    public static synchronized Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}
  • multi-thread 환경에서도 정상 동작
  • But, synchronized 키워드 자체에 대한 비용이 크기 때문에 성능이 떨어질 수도 있음 👉 double checked locking (instance가 null인 경우에만 synchronized가 동작)
// double checked locking
public static Singleton getInstance(){
    if(instance == null){
        synchronized (Singleton.class) {
            if(instance == null){
                instance = new Singleton();
            }
        }
    }
    return instance;
}

5. Bill Pugh Singleton Implementation

inner static helper class를 사용하는 방식

앞선 방식의 문제점들을 대부분 해결한 방식으로, 현재 가장 널리 쓰이는 싱글톤 구현 방법

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

public class demo {
		public static void main(String[] args) {
        Singleton a = Singleton.getInstance();
        Singleton b = Singleton.getInstance();
    }
}
  • SingletonHelper 클래스는 Singleton 클래스가 Load 될 때에도 Load 되지 않다가 getInstance()가 호출됐을 떄 JVM 메모리에 로드되고 인스턴스를 생성한다

6. Enum Singleton

enum 타입을 사용

public enum EnumSingleton {
 
    INSTANCE;
    
    public static void doSomething(){
        //do something
    }
}

public class EnumDemo{
    public static void main(String[] args) {
        EnumSingleton singleton = EnumSingleton.INSTANCE;
				...
    }
}
  • 앞선 1~5번 방식은 사실 완전히 안전하진 않다 👉 직렬화와 역직렬화 문제, 리플렉션 문제
    • 클래스를 역직렬화할 때 새로운 인스턴스가 생성되어 singleton 속성을 위반한다
    • 리플렉션을 이용하면 런타임에 private 생성자에 접근하여 새로운 인스턴스를 생성할 수 있다.
  • enum 타입은 기본적으로 직렬화 가능하므로 Serializable 인터페이스를 구현할 필요가 없고, 리플렉션 문제도 발생하지 않는다.
  • 인스턴스가 JVM 내에 하나만 존재한다는 것이 보장되므로, Java에서 싱글톤을 만드는 가장 좋은 방법으로 권장된다.



  • 직렬화 & 역직렬화 (Serialization & Deserialization)
    • 직렬화: 객체를 직렬화하여 전송 가능한 형태로 만드는 것
    • 역직렬화: 직렬화된 파일 등을 역으로 직렬화하여 다시 객체의 형태로 만드는 것
  • 리플렉션 (Reflection) 구체적인 클래스 타입을 알지 못하더라도 그 클래스의 메서드, 타입, 변수들에 접근할 수 있도록 해주는 자바 API

참고 자료

0개의 댓글

관련 채용 정보