ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 디자인 패턴 (6) - 싱글톤 패턴
    언어/디자인패턴 2023. 9. 11. 16:23

     

    1. 싱글톤 패턴이란?

     

    - 단 하나의 유일한 객체를 만들기 위한 코드 패턴

    - 메모리를 절약하기 위해 인스턴스 필요시 새로 만들지 않고 기존의 인스턴스 가져와 활용하는 기법

    - 보통 싱글톤 패턴이 적용된 객체가 필요한 경우는 리소스를 많이 차지하는 역할을 하는 무거운 클래스들 

     

    ex 데이터 연결 모듈 (스레드풀,커넥션 풀 등등)

     

    1.1 싱글톤 패턴 구현 원리

     

    출처: https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%EC%8B%B1%EA%B8%80%ED%86%A4Singleton-%ED%8C%A8%ED%84%B4-%EA%BC%BC%EA%BC%BC%ED%95%98%EA%B2%8C-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90

     

     - 생성자에 private를 붙여주기만하면된다.

     - 전역변수로 미리 생성된 객체를 얻는다.

     

    1.2 여러가지 싱글톤 패턴 구현 방법

     

    - Eager Initialization

     

     > 가장 직관적이면서 심플한 방법 

     > static final이라 멀티 쓰레드 환경에서도 안전하다.

     > static멤버는 당장 객체 사용하지 않더라도 메모리에 적재하기 때문에 리소스가 큰 객체라면 공간 자원 낭비 발생

     > 예외처리 어렵다

     

    class Singleton{
    
    	private static final Singleton INSTANCE = new Singleton();
        
        //생성자를 private선언
        private Singleton(){}
        
        public static Singleton getInstance(){ return INSTANCE;}
    
    }

     

    - Static block initialization

     

    > static block를 이용해 예외를 잡을 수 있게됨

    > 여전히 static이므로, 공간차지 심하다.

    class Singleton{
    
    	private static Singleton instance;
        private Singleton(){}
        
        //static 블록을 이용한 예외처리
        static{
        	try{
            	instance = new Singleton();
            }catch(Exception e){
            	throw new RuntimeException("싱글톤 객체 생성 오류");
            }
        
        }
        
        public static Singleton getInstance(){
    		retunr instance;    
        }
    
    }

     

    - Lazy initialization (지연 생성)

     

    > 객체 생성 관리 내부적 처리

    > 메서드 호출했을 때 인스턴스 변수의 null 유무에 따라 초기화하거나 있는걸 반환

    > 미사용 고정 메모리 차지의 한계점 극복

    > 쓰레드 세이프하지 않다는 치명적 단점

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

    * 스레드 A,B가 동시에 생성시 A와 B가 동시에 if문을 평가하고, 이에 따라 아직 객체가 만들어지지 않았다고 평가하면 

      초기화 작업을 두번할 수도 있다.

     

    - Thread safe initialization

     

    - synchronized키워드를 통해 Thread-safe하도록 설정

    - 하지만, 매번 객체를 가져올 때 , synchronized 메서드를 호출해야해서 성능 하락 발생할 수 있다.

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

     

    - Double-Checked Locking

     

    > 매번 synchronized 메서드를 실행하는 것이 문제라면 -> 최초 초기화시에만 적용하고 

    > 이미 만들어진 인스턴스 반환시에는 사용 x 기법

    > 이때 인스턴스 필드에 volatile 키워드 붙여줘야 I/O불일치 해결가능

    > but volatile키워드 사용을 위해선 JVM 1.5이상 -> JVM에 따라 여전히 쓰레드 세이프 하지 않아!

     

     

    * volatile키워드

    > Java에서 쓰레드 여러개 사용시 성능을 위해 각각의 쓰레드들은 변수를 메인 RAM으로부터 가져오는게 아님

    > 캐시(Cache) 메모리에서 가져옴 

    > 문제는 비동기로 변수값 캐시에 저장했다가, 각 쓰레드마다 할당되어 있는 캐시 메모리 변수 값이 불일치할 경우가 있다.

    > 그래서 volatile 키워드를 통해 이 변수는 캐시에서 읽지말고, 메인 메모리에서 읽도록 지정해줌 

     

    출처: https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%EC%8B%B1%EA%B8%80%ED%86%A4Singleton-%ED%8C%A8%ED%84%B4-%EA%BC%BC%EA%BC%BC%ED%95%98%EA%B2%8C-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90

    class Singleton{
    
    	private static volatile Singleton instance;
        
        private Singleton(){}
        
        public static Singleton getInstance(){
        	if(instance == null){
            synchronized(Singleton.class){ //메서드에 동기화 x Singleton에 동기화
            	if(instance == null){
                	instance = new Singleton(); // 최초 초기화 동기화 작업이 일어남
                }
            }
            
            }
            return instance; // 최초 초기화되면 앞으로 생성된 인스턴스만 반환
        }
    }

     

    - Bill Pugh Solution(LazyHolder)

     

    > 권장되는 방법 중 하나 

    > 멀티스레드 안전, Lazy Loading도 완경

    > 클래스 안에 내부 클래스를 두어 JVM 클래스 로더 매커니즘과 클래스가 로드되는 시점을 이용한 방법

    > static 메소드에서는 static 멤버만 호출 가능함 -> 내부 클래스를 static으로 설정 

    > 다만 클라이언트가 임의로 싱글톤 파괴가능 ->(직렬화/역질렬화, Reflection API)

     

    class Singleton{
    
    	private Singleton(){}
        
        //static 내부 클래스 사용
        // Holder로 만들어서 클래스가 메모리 로드 안됨 -> getInstance메서드 호출되야 로드됨
        
        private static class SingleInstanceHolder{
        	private static final Singleton INSTANCE = new Sington();
        }
        public static Singleton getInstance(){
        	return SingleInstanceHolder.INSTANCE;
        }
    }

     

    > 내부 static 클래스로 선언 -> 싱글톤 클래스 초기화 시에도 메모리에 로드x

    > 어떤 모듈에서 getInstance() 호출시 SingleInstanceHolder 내부의 static멤버 가져와서 리턴

       (이때 내부 클래스 한번 초기화)

    >  final로 지정하여 다시 값이 할당되지 않도록 방지

     

    -Enum사용 

     

    >권장되는 두가지 방법 중 하나이다.

    > enum은 애초에 멤버 만들 때 private로 한번만 만들고 초기화해서 thread safe함

    > 클라이언트 Reflection 공격에 안전!

    > 싱글톤 클래스를 멀티톤(일반 클래스)로 바꿔야할때 .. 다시 만들어야함

    > 클래스 상속이 필요할 때 enum은 상속 불가 

     

    enum SingletonEnum{
    
    	INSTANCE;
        
        private final Client dbClient;
        SingletonEnum(){
        	dbClient = Database.getClient();
        }
        
        public static SingletonEnum getInstance(){
        	return INSTACNCE;
        }
        public Client getClient{
        	return dbClient;
        }
    
    }

     

    * Enum과 Bill pugh Solution기법 중 하나 사용하자 

     > 성능이 중시 되면: Bill, 직렬화 안정성 중시되면 : Enum

     

    2. 싱글톤은 안티패턴인가?

     

    - 싱글톤은 다음과 같은 문제점이 있다. 메모리 이점과 다음의 문제점간 상충관계를 잘 고려하자 

     

    1. 모듈간 의존성이 높아진다.

     

    > 클래스의 객체를 미리 생성해서 정적 메소드를 이용해 생성 -> 클래스 사이 강한 의존성과 높은 결합 생성

    > 하나의 싱글톤 클래스를 여러 모듈이 공유 --> 싱글톤 인스턴스 변화시 이를 참조하는 모든 모듈 수정해야함

    > 클라이언트 코드가 너무 많은 곳에서 사용하면, 결합도가 너무 높아짐 

     

    2. SOLID 위배 사례가 많다.

     

    - 싱글톤 자체가 하나만 생성됨 따라서 여러 책임 지니는 경우 많다 (SRP 위배)

    - 한 클래스에 너무 많은 책임이 몰리면, 클래스간 결합도가 높아짐-> 클라이언트가 구현 클래스에 의존 -> OCP 위배

    - 위의 결과 DIP도 위반 

     

    3. TDD 단위 테스트가 어렵다.

     

    - 싱글톤 클래스를 사용하는 모듈은 테스트가 어렵다.

     

     

     

    참고자료:

     

    https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%EC%8B%B1%EA%B8%80%ED%86%A4Singleton-%ED%8C%A8%ED%84%B4-%EA%BC%BC%EA%BC%BC%ED%95%98%EA%B2%8C-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90

     

    💠 싱글톤(Singleton) 패턴 - 꼼꼼하게 알아보자

    Singleton Pattern 싱글톤 패턴은 디자인 패턴들 중에서 가장 개념적으로 간단한 패턴이다. 하지만 간단한 만큼 이 패턴에 대해 코드만 던져주고 끝내버리는 경우가 있어, 어디에 쓰이는지 어떠한 문

    inpa.tistory.com

     

Designed by Tistory.