본문 바로가기
  • 주니어 개발자의
    RESTful 성장기
Design Pattern

Singleton Pattern

by 돌건 2022. 1. 13.

Singleton Pattern

인스턴스를 단 한 개만 제공하는 클래스

 

구현 방법

1. private 생성자와 static 메소드를 이용한 구현

public class Singleton {
	
    // 클래스 당 하나만 존재해야하기 때문에 static 필드를 가지도록 한다.
    private static Singleton instance;
    
    // 생성자의 접근 지정자를 private으로 지정함으로써, new를 이용한 생성을 막는다.
    private Singleton() {}
    
    // 생성자를 사용할 수 없으니, 외부에서 인스턴스를 얻기 위해 static으로 지정한다.
    public static Singleton getInstance() {
    	
        if (instance == null) {
            instance = new Singleton();
        }
        
        return instance;
    }
}
이 구현 방법은 Multi Thread 환경에서 안전하지 않다.

만약 여러 개의 스레드에서 동시에 getInstance()를 호출하게 되면 instance가 생성되지 않은 상태이기 때문에 제각기 다른 인스턴스를 생성하게 될 수 있다.

 

2. 동기화를 사용해 Multi Thread 환경에서 안전한 구현

public class Singleton {
	
    private static Singleton instance;
    
    private Singleton() {}
    
    // 메서드에 동기화를 걸어 Lock을 걸어 멀티 스레드 환경에서 안전하게 만든다.
    public static synchronized Singleton getInstance() {
    	
        if (instance == null) {
            instance = new Singleton();
        }
        
        return instance;
    }
}
synchronized를 사용하는 데는 비용이 들어가게 된다. 싱글톤 객체를 생성한 이후에 객체에 접근하기 위해서는 항상 synchronized가 작동하게 된다. 따라서, 싱글톤 객체를 자주 사용해야 하는 경우에는 성능 저하를 야기시킬 수 있다.

 

3. 이른 초기화를 이용한 구현

public class Singleton {

    // 클래스 로딩 타임에 함께 생성된다.
    private static final Singleton INSTANCE = new Singleton();
    
    private Singleton() {}
    
    public static Singleton getInstance() {
    	return INSTANCE;
    }
}

 

싱글톤 객체의 사용 유무와 관련 없이 클래스가 메모리에 올라갈 때(ex.메서드가 호출될 때) 싱글톤 객체가 생성되므로 해당 인스턴스의 생성 비용이 큰 경우 비효율적일 수 있다.

 

4. Double Checked Locking

public class Singleton {

    private static Singleton instance;
    
    private Singleton() {}
    
    public static Singleton getInstance() {
    	
        if(instance == null) {
            synchronized(Singleton.class) {
                if(instance == null) {
                     instance = new Singleton();
                }
            }
        }
        
        return instance;
    }
}
synchronized를 메서드가 아닌 메서드 내부에서 사용함으로써 2번에 걸쳐 instance의 생성 유무를 확인한 뒤 객체를 생성하는 방법이다. 인스턴스가 null인 경우, 즉 생성되지 않았을 때에만 동기화가 실행되도록 함으로써 객체가 생성된 이후에는 동기화가 진행되지 않는다. 이에 따라 동기화 비용을 줄일 수 있게 된다.

 

5. static inner class를 이용한 구현

public class Singleton {

    private Singleton() {}
    
    private static class Holder {
    	private static final Singleton INSTANCE = new Singleton();
    }
    
    public static Singleton getInstance() {
    	return Holder.INSTANCE;
    }
}
Holder 클래스는 inner class로 선언되었기 때문에 Singleton 클래스가 Class Loader에 의해서 로딩될 때가 아닌, getInstance()가 호출될 때 JVM 메모리에 로드되면서 객체를 생성하게 된다. JVM에 로드될 때 객체가 생성되기 때문에 Multi Thread 환경에서도 안전하다.

 

6. Enum 타입을 사용한 구현

public enum Singleton {
    
    INSTANCE;
}
reflection과 직렬화 & 역직렬화를 사용하면 싱글톤 패턴을 깨트릴 수 있다. 이를 방지하기 위해서 사용하는 방법이 바로 Enum 타입으로 싱글톤 패턴을 구현하는 것이다. Enum 클래스는 reflection에서 자체적으로 새로운 인스턴스를 생성할 수 없도록 막아뒀다.

다음으로, enum은 Enum 클래스를 상속 받고 있는데, Enum 클래스는 Serializable 클래스를 상속 받고 있어 직렬화와 역직렬화가 가능하다. 하지만 Enum에 정의되어 있는 readObject() 메서드를 보면, 역직렬화를 진행할 수 없다는 메시지와 함께 예외를 던지기 때문에 새로운 인스턴스를 생성할 수 없다.

readObject() 메서드는 역직렬화를 위해 사용되는 메서드인데, enum이 아닌 클래스에서 역직렬화를 막기 위해서는 싱글톤 객체를 반환하도록 readResolve() 메서드를 정의하면 된다. 이 메서드를 정의하면, readObject()를 통해 생성된 객체는 무시되면서 GC 대상이 된다.

 

 

참고: 백기선 - 코딩으로 학습하는 GoF의 디자인 패턴 (인프런)

 

'Design Pattern' 카테고리의 다른 글

Abstract Factory Pattern  (0) 2022.01.17
Factory Method Pattern  (0) 2022.01.15

댓글