2021. 1. 5. 16:40ㆍ2021/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 |