고정된 메모리 영역을 얻어 한 번의 new로 인스턴스를 사용하자!
싱글톤 패턴이란,
어떤 클래스가 최초 한 번만 메모리를 할당하고 그 메모리에 인스턴스를 만들어 사용하는 디자인 패턴
스레드풀, 캐시, 대화상자, 로그기록 객체 등에 사용되는 패턴입니다.
따라서, 적절히 사용하는 것이 매우 중요한 패턴입니다!
object 키워드를 제공합니다. 이를 통해 최초 호출 시 생성되도록 합니다.
여러 방법이 있는데요, 하나하나 보며 어떤 방법이 가장 나은지 설명하겠습니다.
Eager initialization
class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {return instance();}
}
이 방식은 해당 instance에 접근하지 않아도 생성되어 memory leak을 유발한다는 단점을 갖고 있습니다.
Lazy initialization
class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) instance = new Singleton();
return instance;
}
}
getInstance() 메소드가 호출되어야 초기화를 합니다. 따라서, 위 Eager initialization 방식의 단점을 보완한 것을 확인할 수 있습니다. 하지만 멀티 스레드 환경에서 문제가 없을까요? 동기화 처리가 없어서 두 개 이상의 인스턴스를 생성할 수 있습니다.
class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchrozied Singleton getInstance() {
if (instance == null) instance = new Singleton();
return instance;
}
}
synchrozied 키워드를 사용해 thread-safe하게 동작할 수 있습니다. 하지만 여러 곳에서 공유하라고 싱글톤을 쓰는건데 단일 스레드처럼 동작하는게 비효율적입니다! instance = new Singleton()이 유일한 호출이므로 해당 부분으로 동기화를 최소화 해 더 효율적으로 만들 수 있을거 같습니다!
class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
synchrozied(Singleton.class) {
if (instance == null) instance = new Singleton();
}
return instance;
}
}
static synchronized block을 이용해 동기화 범위를 최소화 했습니다. 이를 통해 synchronized에 의한 성능 저하를 완화했습니다.
이론으로는 완벽해 보입니다. 하지만 자바 플랫폼 메모리 모델에서는 완벽한 작동을 보장할 수 없습니다. 문제가 되는 상황은 다음과 같습니다.
if문 내의 메모리 할당 부분은 #1. 싱글톤 객체를 위한 메모리할당, #2. 메모리를 할당 받아 null이 아닌 상태가 됨. 하지만 아직 초기화 되지 않음. #3. 싱글톤 생성자 실행 의 흐름을 갖습니다. #2까지 진행되고 스레드2가 선점한다면 스레드2는 half initialized state의 instance에 접근하게 됩니다.
이 문제는 volatile 키워드로 해결할 수 있습니다!
class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
synchrozied(Singleton.class) {
if (instance == null) instance = new Singleton();
}
return instance;
}
}
volatile keyword: Java 변수를 main memory에 저장하겠다는 것을 명시. 키워드 사용 시 JVM에 의해 재배치 되지 않는다는 "Happens-before"을 보장
따라서, 동기화로 인한 성능저하를 최소로 하고 thread-safe 한 싱글톤 클래스를 구현해 봤습니다 !!🤓
clone of singleton object
https://www.geeksforgeeks.org/prevent-singleton-pattern-reflection-serialization-cloning/
static vs singleton
https://stackoverflow.com/questions/519520/difference-between-static-class-and-singleton-pattern
Java의 synchronized block은 visibility를 보장한다. 따라서, synchronized block 안에서는 volatile과 같은 처리가 필요하지 않다. 특히 위 코드에서는 read도 synchronized block 안에서 진행하기 때문에 volatile 키워드가 필요하지 않음
ref. https://stackoverflow.com/questions/3214938/java-volatile-modifier-and-synchronized-blocks
read 를 밖으로 뺀다면? -> volatile, double checking 필요
class Singleton {
private static volatile Singleton instance;
}