ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 싱글톤 디자인 패턴에 대해
    One Cookie a day 2023. 10. 16. 19:36

    🎾 싱글톤 패턴이란 무엇인가? 

    • 하나의 클래스 당 하나의 인스턴스만을 가지고 재사용하는 디자인 패턴을 말한다. 
    • 하나의 인스턴스만 사용하기 때문에 메모리를 효율적으로 사용하고, 객체들 간 데이터 공유가 쉽지만, 그만큼 여러 단점도 가지고 있어 안티패턴으로 불리기도 한다. 
    • 첫 번째로, 싱글톤 패턴을 구현하는 코드들이 많이 필요하며,  (멀티스레드 환경에서의 동시성 문제 해결을 위한 코드 등) 테스트 시 매번 인스턴스 상태를 초기화하는 작업이 필요하다. 
    • 싱글톤 객체가 너무 많은 로직을 가지고 있어 여러 객체들이 공유해서 사용한다면, 객체들 간 결합도가 높아져서 OCP(개방폐쇄원칙 - 확장에는 열려있고 수정에는 닫혀있다, 기존의 코드를 변경하지 않으면서 기능을 확장할 수 있어야 한다) 위배할 확률이 높다. 

     

     

    🎾 어떻게 구현하는가 ? 

    🎾 흔히 알려져 있는 방식

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

    이 방식의 문제점? 

    • Thread Unsafety
      • if (null == intance) 인 시점에 여러 스레드가 동시에 접근한다면, 접근한 스레드들 모두 인스턴스를 생성하게 될 것이고, 싱글톤이 깨지게 된다. 

     

    🎾 Eager 초기화

    public class Singleton {
        private static Singleton instance = new Singleton();
        private Singleton() {};
    	
    	public static getInstance() {
    		if (null == instance) {
    			instance = new Singleton();
    		}
    		return instance;
    	}
    }
    • 미리 프로그램 실행 전 싱글톤 인스턴스를 생성해두는 방식 
    • 단, 사용하지 않는 인스턴스들도 다 메모리를 잡아두고 있으므로 메모리 측면의 비효율성이 존재한다. 

     

    🎾 synchronzied 사용 (DCL)

    public class Singleton {
        private volatile static Singleton instance;
        
        private Singleton() {};
    
        public static Singleton getInstance() {
            if (null == instance) {
                synchronized (Singleton.class) {
                    if (instance == null) {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
    • getInstance 내부에서 instance 생성하는 부분만 synchronized 키워드로 처리
    • 인스턴스 생성 여부를 확인하고, 생성부분만 lock을 걸어 다시 확인해 본 다음 생성한다.
    • Volatile 키워드란? (jdk 1.5 ~ )
      • 각 스레드가 가진 CPU cache에서 값을 가져오지 않고, 공유하는 Main memory에서 읽고 쓰는 방법
      • 만약 이 키워드가 없다면, 멀티 스레드 간 각각의 CPU 캐시로부터 읽어오기 때문에 변수 값의 불일치 문제가 발생할 수 있다.
      • 그래서 Volatile 키워드를 통해 메모리에서 읽어오게 함으로써,  스레드 간 읽어온 값 불일치 문제 없이 싱글톤이 제대로 수행되도록 만들어준다. 
      • 참고로 단순 Volatile로 동시성 문제를 해결하려면 쓰기 작업을 수행하는 스레드는 하나여야 함.

     

     

    🎾 Lazy Holder

    public class Singleton {
    	private Singleton() {};
    	private static Singleton getInstance() {
    		return LazyHolder.singleton;
    	}
    	
    	private static class LazyHolder {
    		private static final Singleton singleton = new Singleton(); 
    	}
    
    }
    • 특정 스레드가 getInstance 호출 시점에 Lazyholder의 singleton이 초기화되어 리턴하므로, 실제 객체가 필요한 시점까지 초기화를 미룬다는 점에서 성능 문제 해결 
    • LazyHolder 가 어떻게 Lazy의 장점 + static 영역의 장점 다 갖출 수 있는가? 
      • 가장 먼저 껍데기인 싱글톤 클래스는 클래스 로더에 의해 로딩된다. (실제 메모리에 올라오지는 않음)
      • 내부의 LazyHolder는 호출되지 않았기 때문에 코드상으로 남아있음
      • 특정 스레드가 getInstance() 메소드를 호출할 경우 LazyHolder를 리턴하라는데? 하고 다시 클래스로더로 찾아감
      • 그제서야 new 생성자를 통해 생성이된다. 

     

     

    🎾 Enum 방식

    public enum Singleton {
    	Instance
    }
    
    // 사용시 아래처럼 
    Singleton singleton = Singleton.Instance;

     

    enum은 아래 상황에서 싱글톤이 깨지는 문제가 발생하지 않는다. 위의 코드들에서 생성자를 private으로 막아두었지만, 리플렉션을 통해 접근이 가능하며, 직렬화를 사용한다면 2번째 상황에서 문제도 발생한다. 

    • 리플렉션(private으로 생성된 메소드들도 런타임 시 동적으로 접근 가능) 상황
    • 직렬화 된 객체를 역직렬화하는 과정에서 새로운 객체가 생성되는 상황

    오라클 문서에 나와있는 항목을 보면 알 수 있듯, enum 타입은 정의된 타입 하나만 생성되고 직렬화-역직렬화 과정에서 복사된 객체가 생성되지 않는다. JVM 내에 하나만 존재하도록 보장되므로 싱글톤 만드는 방법으로 권장된다. 

    더보기

    An enum type has no instances other than those defined by its enum constants. It is a compile-time error to attempt to explicitly instantiate an enum type

    The final clone method in Enum ensures that enum constants can never be cloned, and the special treatment by the serialization mechanism ensures that duplicate instances are never created as a result of deserialization. 

    Reflective instantiation of enum types is prohibited. Together, these four things ensure that no instances of an enum type exist beyond those defined by the enum constants.

    대신, enum field의 내부 값 / 메소드들은 직렬화 대상이 아니므로 유실된다. 이점을 기억하기(https://docs.oracle.com/javase/6/docs/platform/serialization/spec/serial-arch.html#6469)

     

     

    🎾 그럼 실무에서는 싱글톤을 구현하기 위해 어떤 방식을 쓰는 것인가? 

    • 주로 LazyHolder 방식을 사용한다.
    • Enum을 오라클 문서에서 권장하고 있기는 하지만, 서비스 로직 단에서 예외처리가 쉽지 않아 주로 실무에서는 LazyHolder를 사용한다고 한다. 

     

Designed by Tistory.