Singleton 패턴에 대해 공부하다보면 쓰레드로부터 안전한 코드를 작성해야 한다는 이야기를 들어보셨을 것입니다.
그덕에 알게되는 여러가지 방법이 있습니다.
- Enum을 이용해 Singleton을 보장한다.
- synchronized 키워드를 메서드에 추가한다.
- synchronized 블럭을 lazy 생성 조건문 내부에 추가한다.
- 내부 정적 클래스를 구현하여 내부 클래스에서 생성된 인스턴스를 가져온다.
이 방법들 중에서도 1 ~ 3 번은 각각 단점들이 존재하여 현재는 잘 사용하지 않는 다고 알려져 있습니다.
Enum 같은 경우는 상태 정보를 가질 수 없고 synchronized 키워드는 에러 혹은 성능저하를 동반한다.
덕분에 우리는 4번 내부 정적 클래스를 구현하여 내부 클래스에서 생성된 인스턴스를 가져온다를 주로 사용하게 됩니다.
흔히 BillPughSingleton이라고도 알려진 이 패턴은 멀티 스레드 환경에서 안전하게 싱글턴을 보장한다고 하는데요.
그렇다면 왜 이 패턴이 Thread-safe 할까요?
LazyHolder
먼저 LazyHolder를 이용해 싱글턴을 구현해봅시다.
public class SingleTon {
private SingleTon() {
}
private static class LazyHolder {
private static SingleTon INSTANCE = new SingleTon();
private LazyHolder() {
}
}
public static SingleTon getInstance() {
return LazyHolder.INSTANCE;
}
}
이 코드는 Singleton이라는 객체가 필요할 때 초기화를 하는 기법입니다.
Singleton 클래스가 먼저 로드되더라도 해당 클래스에는 LazyHolder 관련 코드가 존재하지 않기 떄문에 inner class를 초기화 하지 않습니다.
즉 getInstance메서드를 호출하는 순간 LazyHolder 클래스를 참조하여 JVM에 해당 클래스가 로딩되는 것이죠.
여기서 이 패턴이 Thread-safe을 보장할 수 있는 핵심이 등장합니다. 바로 Class 로딩입니다.
Class Load
클래스가 로딩되는 시점은 Thread-safe를 보장합니다. 그 이유는 뭘까요?
Java docs의 ClassLoader
에서 일부 발췌한 내용을 간단하게 요약하면 아래와 같습니다.
ClassLoader.registerAsParallelCapable 메서드를 사용하여 클래스를 로딩하면 병렬 가능한 클래스 로더가 보장된다
Java 7 버전부터 ClassLoader는 내부의 ParallelLoaders 클래스를 사용하여 parallel capable한 클래스 로더를 등록하고 있습니다.
/**
* Registers the given class loader type as parallel capabale.
* Returns {@code true} is successfully registered; {@code false} if
* loader's super class is not registered.
*/
static boolean register(Class<? extends ClassLoader> c) {
synchronized (loaderTypes) {
if (loaderTypes.contains(c.getSuperclass())) {
// register the class loader as parallel capable
// if and only if all of its super classes are.
// Note: given current classloading sequence, if
// the immediate super class is parallel capable,
// all the super classes higher up must be too.
loaderTypes.add(c);
return true;
} else {
return false;
}
}
}
설명을 읽어보면 subclass에 대해서는 어느정도 제약이 있지만 대부분 클래스를 로딩하는 순간에는 Thread-safe를 보장해주는 것입니다.
정리
Singleton 패턴을 멀티쓰레드 환경으로부터 안전하게 사용하려면 LazyHolder 패턴을 사용하라.
클래스가 로딩될 때는 thread-safe이 보장된다. 궁금하면 java doc ClassLoader를 읽어보자
'우아한테크코스 4기 > 레벨1' 카테고리의 다른 글
[Java] db 테스트 (0) | 2022.04.02 |
---|---|
[Java] JDBC API Statement vs PreparedStatement (1) | 2022.03.29 |
[Java] 전략패턴 (0) | 2022.03.15 |
[Java] 의존성 역전 원칙 (0) | 2022.03.07 |
[Java] Synchronized 이해하기 (0) | 2022.03.05 |