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

DyungE_100·2022년 5월 11일
0

Design Pattern

목록 보기
1/1
post-thumbnail

1. 싱글톤 패턴(Singleton Pattern)이란?

애플리케이션이 시작될 때 어떤 클래스가 최초 한 번만 메모리를 할당하고, 그 메모리에 인스턴스를 만들어 사용하는, 생성자가 여러 차례 호출되더라도 실제로 생성되는 객체는 하나인 디자인 패턴이다. 예를 든다면 레지스트리 같은 설정 파일의 경우 객체가 여러 개로 생성되면 설정 값이 바뀔 위험이 있다.

아래는 코드 예시이다.

public class SignletonPatternEx {
	// Instance
    public static SignletonPatternEx instance = new SignletonPatternEx();
    
    // private Constructor
    private SignletonPatternEx() {}
    
    // get method
    public static SignletonPatternEx getInstance() {
    	return instance;
    }
}

1.2 싱글톤 패턴의 장점

  • 싱글톤 패턴은 한 번의 생성으로 객체를 계속해서 재사용이 가능하기 때문에 메모리 낭비를 방지할 수 있다.

  • 싱글톤으로 생성된 객체는 한 번의 생성으로 전역성을 띄기에 다른 객체와 데이터를 공유가 용이하다. 따라서 DBCP(Database Connection Pool)처럼 공통된 객체를 여러 개 생성해야 하는 경우에 많이 사용된다.(Thread Pool, Cache, 사용자 설정, 로그 기록 등)

  • 인스턴스가 절대적으로 하나만 존재하는 것을 보증하고 싶을 경우 사용한다.

  • 한 번 생성 이후 두 번째 사용 시부터는 메모리에 할당하는 과정이 없어 인스턴스 로딩 시간이 줄어 성능이 좋아진다.


1.3 싱글톤 패턴의 단점

  • 싱글톤 인스턴스가 너무 많은 일을 하거나 많은 데이터를 공유시킬 경우, 다른 클래스의 인스턴스와의 결합도가 높아져 개방-폐쇄의 원칙(OCP)를 위반하게 될 가능성이 높다.
    이는 객체 지향 설계 원칙에 어긋나는 일이며 수정이 어려워지고, 유지보수의 비용이 높아질 수 있다.

  • 멀티 쓰레드 환경에서 동기화 처리를 안하게 되면 인스턴스가 2개가 생성되는 문제가 발생할 수 있으며, 동기화 처리를 하더라도 성능 저하의 문제가 발생할 수 있다.


2. 다양한 싱글톤 구현

싱글톤을 구현하는 방법은 몇 가지가 있는데 아래와 같이 구현할 수 있다.

Static block

public class SingletonPatternEx {
	// Instance
    private static SingletonPatternEx instance;
    
    // private Constructor
    private SingletonPatternEx() {}
    
    static {
    	try { instance = new SingletonPatternEx(); } 
        catch(Exception e) { throw new RuntimeException("Create instance fail."
        + "error msg=" + e.getMessage()); }
    }
    
    public static SingletonPatternEx getInstance() {
    	return instance;
    }
}

static을 이용하면 클래스가 로드될 때 한 번만 실행되고 Method Area에 메모리 할당을 받게 된다. 하지만 인스턴스가 사용되는 시점이 아닌 클래스 로딩 시점에 실행이 되므로 많이 사용하게 되면 메모리 낭비가 될 수 있다.

Lazy init

static 방법에서 개선하여 클래스 로딩 시점이 아닌 인스턴스가 생성하고자 할 때 생성되는 싱글톤 패턴 구현 방법이다.

public class LazyInitialization {
	// Instance
    public static LazyInitialization instance;
    
    // private Constructor
    private LazyInitialization() {}
    
    // get method
    public static LazyInitialization getInstance() {
    	if (instance == null) { instance = new LazyInitialization(); }
    	return instance;
    }
}

private static으로 인스턴스 변수를 만들고, private 생성자로 외부에서 생성하는 것을 막았다.

하지만 위의 형태로 구성할 경우 멀티 쓰레드 환경에 취약하다.
만약 instance가 생성되지 않은 상태이고 Thread1이 getInstance() 메서드를 호출하면 instance 변수는 null인 상태일 것이다. 하지만 Thread2가 아직 instance의 인스턴스 생성 전에 if문을 실행하여 instance 변수가 null인지 확인하게 되면 이 또한 인스턴스를 생성하는 생성자를 호출하는 코드를 실행하게 된다.
결과적으로 다중 쓰레드가 여러 인스턴스를 생성하게 된다.

Thread safe Lazy Initialization

public class ThreadSafeLazy {

    public static ThreadSafeLazy instance;
    
    private ThreadSafeLazy() {}
    
    public static synchronized ThreadSafeLazy getInstance() {
    	if (instance == null) { instance = new ThreadSafeLazy(); }
    	return instance;
    }
}

이번에는 synchronized 키워드를 사용하여 thread-safe하게 만들었다.
하지만 synchronized 특성상 비교적 큰 성능 저하가 발생하므로 권장하지 않는 방법이다.

Thread safe Lazy Iniatialization + Double-checked locking

public class ThreadSafeLazyDouble {

	public static ThreadSafeLazyDouble instance;
    
    private ThreadSafeLazyDouble() {}
    
    public static ThreadSafeLazyDouble getInstance() {
        if (instance == null) {
        	synchronized (ThreadSafeLazyDoublce.class) {
            	if ( instance == null) instance = new ThreadSafeLazyDouble(); }
        }
    	return instance;
    }
}

이번에는 getInstance() 메서드에 synchronized를 사용하는 것이 아니라 첫 번째 if문으로 인스턴스의 존재여부를 체크한 다음 동기화 시켜 인스턴스를 생성하더라도 thread-safe하면서 처음 생성 이후 synchronized 블럭을 타지 않게 하여 성능 저하를 완화하는 방법이다.

Initialization on demand holder idiom(holder에 의한 초기화)

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

클래스 안에 클래스(Holder)를 두어 JVM의 Class loader 매커니즘과 class가 로드되는 시점을 이용한 방법이다. 가장 많이 쓰이고 일반적인 Singleton 패턴 구현이며, JVM의 클래스 초기화 과정에서 보장되는 원자적 특성을 이용한 방법이다.

cf) Java와 Spring에서의 싱글톤 차이점은 생명주기가 다르다. 또한 Java에서 공유 범위는 Class Loader 기준이지만, Spring에서는 ApplicationContext(즉, 서블릿)가 기준이 된다.




https://devmoony.tistory.com/43

https://gmlwjd9405.github.io/2018/07/06/singleton-pattern.html

https://jeong-pro.tistory.com/86

https://elfinlas.github.io/2019/09/23/java-singleton/

0개의 댓글