2020. 12. 11. 16:04ㆍ백엔드/Java_디자인패턴
싱글턴 패턴
- 인스턴스가 오직 1개만 생성되어야 하는 경우 사용되는 패턴.
- 싱글턴을 만들 때 동시성(Concurrency) 문제를 고려해서 설계해야 함
- 멀티 스레딩 환경헤도 동작 가능하게끔 구현해야 함.
- private constructor를 가짐.
- static method를 사용함.
- 보통 가장 많이 사용되는 싱글턴 구현 방식은 LazyHolder 방식임.
싱글턴 패턴 사용시 주의사항
클래스 로더를 2개 이상 사용하는 경우, 인스턴스가 2개 이상 생성될 수 있기 때문에 이런 경우에는 클래스 로더를 지정해야 함.
자바와 스프링의 싱글턴 차이점은, 자바에서 싱글톤 객체의 생명주기는 클래스 로더가 기준이지만, 스프링에서는 어플리케이션 컨텍스트(ApplicationContext)가 기준이 됨.
클래스 로더 기준이라는 것은 톰캣이 WAR파일을 만들게 되면, WAR파일 하나 당 클래스 로더 하나 1:1식으로 배치가 되기 때문에 다른 WAR파일은 참조가 불가능함.
반면, ApplicationContext기준이라는 것은 web.xml에서 root context하나와 servlet context 여러개를 등록할 수 있는데, 이 각각의 context들이 싱글턴 범위가 됨.
Eager Initialization(이른 초기화)
static 키워드의 특징을 이용해서 클래스 로더가 초기화 하는 시점에서 정적 바인딩(static binding:컴파일 시점에서 성격이 결정됨)을 통해 해당 인스턴스를 메모리에Singleton 등록해서 사용하는 방법.
이른 초기화 방식은 클래스 로더에 의해 클래스가 최초로 로딩 될 때 객체가 생성되기 때문에 Thread-safe함.
public class Singleton{
//Eager Initialization
private static Singleton uniqueInstance = new Singleton();
private Singleton();
public static Singleton getInstance(){
return uniqueInstance;
}
}
Lazy Initialization with synchronized(동기화 블럭)
synchronized 키워드를 이용한 게으른 초기화 방식인데, 게으른 초기화 방식이란, 컴파일 시점에 인스턴스를 생성하는 것이 아니라, 인스턴스가 필요한 시점에 요청하여 동적 바인딩(dynamic bunung:런타임시에 성격이 결정됨)을 통해 인스턴스를 생성하는 방식.
메서드에 동기화 블럭을 지정해서 Thread-safe하지만 인스턴스가 생성되었든 안되었던 무조건 동기화 블럭을 거치게 되고, synchronized키워드를 사용하면 성능이 약 100배 가량 떨어짐.
public class Singleton{
private static Singleton uniqueInstance;
private Singleton(){}
//Lazy Initialization
public static synchronized Singleton getInstance(){
if(uniqueInstance == null){
uniqueInstance = new Singleton();
}
return uniqueInstance
}
}
Lazy Initialization.Double Checking Locking(DCL)
위 동기화 블럭 방식을 개선한 방식. 이 방식은 인스턴스가 생성되지 않은 경우에만 동기화 블럭이 실행되게끔 구현하는 방식.
코드를 살펴보면, volatile 키워드가 등장하는데, volatile 키워드를 사용하면, 멀티스레딩을 쓰더라도 uniqueInstance변수가 Singleton인스턴스로 초기화 되는 과정이 올바르게 진행되도록 할 수 있음.
volatile 키워드가 필요한 이유는, volatile 변수를 사용하고 있지 않는 멀티 스레드 어플리케이션에서는 작업을 수행하는 동안 성능 향상을 위해 Main Memory에서 읽은 변수 값을 CPU Cache에 저장하게 되는데 만약에 멀티 스레드 환경에서 스레드 변수 값을 읽어올 때 각각의 CPU Cache에 저장된 값이 다르기 때문에 변수 값 불일치 문제가 발생하게 되는데, volatile 변수를 사용할 경우 Main Memory에 값을 저장하고 읽어오기 때문에(read and write) 변수 값 불일치 문제가 생기지 않음.
( 멀티 스레드 환경에서 하나의 스레드는 read and write하며, 나머지 스레드는 read만 하는 경우 변수의 최신 값을 보장.
멀티 스레드 환경에서 여러 개의 스레드가 write하는 상황이하면 동기화 블럭(synchronized)을 지정해서 원자성(atomic)을 보장해야 함. )
public class Singleton{
private volatile static Singleton uniqueInstance;
private Singleton() {}
//Lazy Initialization. DCL
public Singleton getInstance(){
if(uniqueInstance == null){
synchronized(Singleton.class){
if(uniqueInstance == null){
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
Lazy Initialization.Enum(열거 상수 클래스)
Enum 인스턴스의 생성은 기본적으로 Thread-safe함. 따라서 스레드 관련된 코드가 없어져서 코드가 간단해지지만, Enum 내의 다른 메서드가 있는 경우에 해당 메서드가 Thread-safe한지는 개발자가 책임져야 함.
Enum 방식을 사용한 장점은 아주 복잡한 직렬화 상황이나, 리플렉션 공격에도 제 2의 인스턴스가 생성되는 것을 막아줌단, 만들려는 싱글턴이 Enum외의 클래스를 상속해야 하는 경우에는 사용할 수 없고 Android 같이 Context의존성이 있는 환경일 경우, 싱글턴의 초기화 과정에 Context라는 의존성이 끼어들 가능성이 있음.
public enum Singleton{
INSTANCE;
}
Lazy Initialization.LazyHolder(게으른 홀더)
LazyHolder방식은 가장 많이 사용되는 싱글턴 구현 방식.
volatile이나 synchronized 키워드 없이도 동시성 문제를 해결하기 때문에 성능이 뛰어남.
싱글턴 클래스에는 InnerInstanceClazz 클래스의 변수가 없기 때문에, static 멤버 클래스더라도, 클래스 로더가 초기화 과정을 진행할 때 InnerInstanceClazz메서드를 초기화 하지 않고, getInstance() 메서드를 호출할 때 초기화 됨. 즉, 동적바인딩(Dynamic Binding:런타입시에 성격이 결정)의 특징을 이용하여 Thread-safe하면서 성능이 뛰어남.
InnerInstanceClazz내부 인스턴스는 static이기 때문에 클래스 로딩 시점에서 한번만 호출된다는 점을 이요한 것이며, final을 써서 다시 값이 할당되지 않도록 함.
public class Singleton{
private Singleton(){
/**
*static member class
*내부클래스에서 static변수를 선언해야하는 경우 static내부 클래스를 선언해야 함.
*static멤버, 특히 static 메서드에서 사용될 목적으로 선언
*/
private static class InnerInstanceClazz(){
//클래스 로딩 시점에서 생성
private static final Singleton uniqueInstance = new Singleton();
}
public static Singleton getInstance(){
return InnerInstanceClazz.uniqueInstance;
}
}
출처: medium.com/webeveloper/%EC%8B%B1%EA%B8%80%ED%84%B4-%ED%8C%A8%ED%84%B4-singleton-pattern-db75ed29c36
'백엔드 > Java_디자인패턴' 카테고리의 다른 글
[Java_디자인패턴] 팩토리 패턴(Factory Pattern) (2) | 2020.12.05 |
---|