디자인 패턴

Singleton Pattern - 생성

iksadnorth 2023. 7. 19. 14:56

👣 문제 상황

필드에 데이터를 가공하고 보유하는 클래스가 아닌 메서드를 사용하기 위해서 사용하는 클래스의 경우 굳이 여러 개의 객체를 만들어서 사용할 이유가 전혀 없다. 오히려 여러 개를 만들면 GC의 가동 빈도만 늘리거나 최악의 경우 메모리 누수도 일으킬 수 있기 때문에 객체를 굳이 2개 이상으로 만들 필요가 없는 경우는 많다.

👣 해결 방법

자바의 경우 7가지 방법이 있다.

1. Eager Initialization - 이른 초기화
정적 필드를 이용한다.

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

	public Singleton getInstance() {
    	return INSTANCE;
    }
}

2. Static Block Initialization - 정적 블록 초기화
초기화 시, 좀 더 자세한 컨트롤이 필요한 경우[예외 처리, 조건부 객체 주입, ...]에 사용하는 초기화

public class Singleton {
	private static final Singleton INSTANCE;
    
    private Singleton() {}
    
    static {
    	try {
        	INSTANCE = new Singleton();
        } except(Exception e) {
        	throw new RuntimeException("Error initializing Singleton");
        }
    }
    
    public static Singleton getInstance() {
    	return INSTANCE;
    }
}

3. Lazy Initialization - 느린 초기화
아직 사용하지도 않는데
Runtime Data Area의 Method Area 메모리와 Heap 메모리를 축내는
1번, 2번 방법을 보완한 방법.

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

4. Thread-Safe Lazy initialization - 다중 스레드의 느린 초기화
final을 사용하지 않아 불안전한 3번 방법을 보완한 방법.

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

5. Double-Checked Locking - 이중 검사 락
4번 방법은 getInstance를 호출할 때마다 동기화 블럭을 사용하므로 성능 저하가 일어날 수 밖에 없다.
때문에 첫 초기화 때만 동기화 블럭을 사용하고자 해당 방법을 사용.

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

6. Bill Pugh Singleton - 빌 퓨 싱클턴
내부 중첩 클래스를 이용하면 위 방법들의 장점만 가져올 수 있다.
getInstance를 호출하기 전까지는 인스턴스 초기화가 이뤄지지 않아 Lazy하고
내부 정적 클래스의 초기화는 클래스 로딩 시점에 한 번만 수행되므로 JVM 차원에서 Thread-Safe하다.
심지어 구현도 쉽다.

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

7. Enum Singleton - 열거형 싱글턴
 enum 타입은 기본적으로 직렬화 가능하므로 Serializable 인터페이스를 구현할 필요가 없고,
리플렉션 문제도 발생하지 않는다.

public enum Singleton {
    INSTANCE;
}

👣 결과

  • 객체를 1개만 만들어 메모리를 효율적으로 사용함.
  • 다만 TDD를 수행할 때, 객체가 1개이다 보니 테스트 순서에 따라 결과값이 달라질 수 있다.

'디자인 패턴' 카테고리의 다른 글

Observer Pattern - 행위  (0) 2023.07.19
Strategy Pattern - 행위  (0) 2023.07.19
Factory Pattern - 생성  (0) 2023.07.19
디자인 패턴이란?  (0) 2023.07.19
라이브러리 vs 프레임워크  (0) 2023.07.19