디자인패턴_Singleton Pattern of Creational Pattern

2021. 1. 5. 16:402021/JOB DA STUDY

애플리케이션이 시작될 때 어떨 클래스가 최초로 한번만 메모리를 할당(Static)하고 그 메모리에 인스턴스를 만들어 사용하는 디자인 패턴으로,

생성자가 여러차례 호출되더라도 실제 생성되는 객체는 하나이며 최초 생성 이후에 호출된 생성자는 최초 생성된 객체를 반환 

---------------------------------------------------------------------------------------------------------------------------

자바에서는 생성자를 Private으로 선언해 더 이상 생성되는 것을 불가하게 하고, getInstance()로 받아쓰게한다.

---------------------------------------------------------------------------------------------------------------------------

 

특징

  • private constructor를 가지며, static method를 사용한다.
  • 동시성(Concurrency) 문제를 고려해서 싱글톤 설계

Why?

  • 싱글톤 패턴의 경우 고정된 메모리 영역을 얻으면서 한번의 new로 인스턴스를 사용하므로, 메모리 낭비 방지 가능
  • 싱글톤으로 만들어진 클래스의 인스턴스는 전역 인스턴스이므로 다른 클래스의 인스턴스들이 데이터 공유하기 쉽다.
  • DBCP(DataBase Connection Pool)처럼 공통된 객체를 여러개 생성해 사용해야하는 상황에서 많이 사용
  • 안드로이드 앱의 경우 각 액티비티나 클래스별로 주요 클래스들을 일일이 전달하기 번거러운데 싱글톤 클래스를 만들면 어디서나 접근하도록 설계할 수있어 편하다.
  • 인스턴스가 절대적으로 하나만 존재하는 것을 보증하고 싶은 경우 사용
  • 객체 생성 후, 두번째 이용부터는 여러 스레드가 동시에 해당 인스턴스를 공유하여 사용할 수 있으므로, 객체 로딩 시간이 현저하게 줄어 성능이 상승

Problem

  • 싱글톤 인스턴스가 너무 많은 일을 하거나 많은 데이터를 공유하는 경우, 다른 클래스 인스턴스간의 결합도가 높아져 "개방-폐쇄 원칙(소프트웨어 개체(클래스, 모듈, 함수  등)는 확장에 대해 OPEN, 수정에 대해 CLOSE)"을 위배
  • 수정이 힘들어지고, 테스트하기 힘들어진다.
  • 멀티스레드 환경에서 동기화 처리를 하지 않으면 인스턴스가 두개 생성되는 경우가 발생할 수 있다.

 

주의

클래스 로더를 2개 이상 사용하는 경우, 인스턴스가 2개 이상 생성될 수 있다. 이런 경우 클래스 로더를 지정해야한다.

JAVA에서는 범위가 클래스 로더 기준인데, 이때문에 톰캣이 WAR파일을 만들게 되면 WAR파일 하나 당 클래스 로더 하나를 1:1로 배치해 다른 WAR파일은 참조가 불가능하다.

Eager Initialization (이른 초기화, Thread-safe)

static 키워드의 특징을 이용해 클래스 로더가 초기화 하는 시점에서 static binding(정적 바인딩)을 통해 해당 인스턴스를 메모리에 등록해서 사용하는 것

 

싱글톤 구현시, 멀티 스레드 환경에서도 동작하게끔 구현해야 한다는 것이다. 즉 Thread-safe 보장이 중요하다.

이른 초기화 방식은 클래스 로더에 의해 클래스가 최초로 로딩될 때 객체가 생성되기 때문에 Thread-safe하다.

public class Singleton{
    private static Singleton uniqueInstance = new Singleton();
    private Singleton(){}
    public static Singleton getInstance(){
        return uniqueInstance;
    }
}

 

Lazy Initialization with synchronized(동기화 블록, Thread-safe)

synchronized키워드를 이용한 Lazy Initialization 방식으로, 메소드에 동기화 블록을 지정해 Thread-safe를 보장한다.

 

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

동기화 블록을 지정한 Lazy Initialization 방식은 Thread-safe하지만

인스턴스가 생성되었든 안되었든 무조건 동기화 블록을 거쳐야한다는 것이다. 

그래서 synchronized 키워드를 사용하면 getInstance 메소드의 성능은 약 100배 가량 떨어지게 된다.

 

Lazy Initialization. Double Checking Locking(DCL, Thread-safe)

동기화 블록 방식을 개선한 방식으로 인스턴스가 생성되지 않은 경우에만 동기화 블록이 실행되겠끔 구현하는 방식

public class Singleton{
    private volatile static Singleton uniqueInstance;
    private Singleton(){}
    public Singleton getInstance(){
        if(uniqueInstance == null){                      
            synchronized(Singleton.class){              
                if(uniqueInstance == null){              
                    uniqueInstance = new Singleton();
                }
           }
      }
     return uniqueInstance;
   }
}
Volatile 키워드

volatile 변수를 사용하지 않는 멀티스레드 어플리케이션에서는 작업을 수행하는 동안 성능의 향상을 위해 Main Memory에서 읽은 변수 값을 CPU 캐시에 저장하게 된다.

그런데 멀티 스레드 환경에서 스레드가 변수값을 읽어 올 때, 각각의 CPU 캐시에 저장된 값이 달라 변수값의 불일치 문제가 발생하게 되는데,

volatile 변수는 Main Memory에 값을 저장하고, 읽어오기 때문에 변수값의 불일치 문제가 발생하지 않는다.

-----------------------------------------------------------------------------------------------------------------------------------

volatile은 멀티스레드를 쓰더라도 uniqueInstance 변수가 Singleton 인스턴스로 초기화되는 과정

private volatile static Singleton uniqueInstance; 이 올바르게 진행 될 수 있도록 한다.

-----------------------------------------------------------------------------------------------------------------------------------

  • 멀티 스레드 환경에서 하나의 스레드는 Read and Write하며, 나머지 스레드는 read만 하는 경우 -> 변수의 최신 값 보장
  • 멀티스레드 환경에서 여러개의 스레드가 Write하는 상황 -> 동기화 블록(Synchronized)을 지정해서 원자성을 보장

 

Lazy Initialization.Enum(열거 상수 클래스, Thread-safe)

Enum 인스턴스 생성은 기본적으로 Thread-safe하다. 그래서 스레드 관련 코드가 없어져서 코드가 간단해진다.

하지만 Enum 내의 다른 메소드가 있는 경우 해당 메소드가 Thread-safe한지는 개발자의 몫이다.

public enum Singleton{
    INSTANCE;
}
  • 복잡한 직렬화 상황, 리플렉션 공격에도 제 2의 인스턴스 생성을 막아준다. 
  • Enum 외의 클래스 상속을 해야하는 경우 사용 불가하다.
  • Android와 같이 Context 의존성이 있는 환경의 경우 싱글턴 초기화 과정에서 Context 라는 의존성이 끼어들 가능성이 있다.

 

Lazy Initialization.LazyHolder(게으른 홀더, Thread-safe)

volatile이나 synchronized 키워드 없이 동시성 문제 해결이 가능하기 때문에 성능이 뛰어나며, 많이 사용한다.

public  class Singleton{
    private Singleton(){}
    private static class InnerInsranveClas(){                                    
    //클래스 로딩 시점에서 생성
        private static final Singleton uniqueInstnace = new Singleton();
    }                                                                                    
    public static Singleton getInstance(){
        return InnerInstanceClas.instance

싱글톤 클래스에는 InnerInstanceClas 클래스의 변수가 없기 때문에, static 멤버 클래스더라도, 클래스 로더가 초기화 과정을 진핼할 때 InnerInstanceClas 메서드를 초기화 하지 않고, getInstance() 메소드를 호출할 때 초기화된다.

(InnerInstanceClas 내부 인스턴스는 static 이기 때문에 클래스 로딩 시점에 한번만 호출된다는 점을 이용한 것이며, 

final을 써서 다시 값이 할당되지 않도록 한다.)

 

동적 바인딩(Dynamic Binding)의 특징 (런타임시 성격 결정)을 이용해 Thread-safe해 성능이 뛰어남

 

 

 

 

 

 

 

 

 

 

 

 

 

'2021 > JOB DA STUDY' 카테고리의 다른 글

디자인패턴_AbstractFactory Pattern of Creational Pattern  (0) 2021.01.06
디자인패턴_Strategy Pattern of Behavioral Pattern  (0) 2021.01.06
Final  (0) 2021.01.05
Static  (0) 2021.01.05
Design Pattern  (0) 2021.01.05