본문 바로가기
우아한테크코스 4기/레벨1

[Java] Singleton LazyHolder는 왜 Thread safe 한가?

by 나는후니 2022. 3. 16.

Singleton 패턴에 대해 공부하다보면 쓰레드로부터 안전한 코드를 작성해야 한다는 이야기를 들어보셨을 것입니다.

그덕에 알게되는 여러가지 방법이 있습니다.

  1. Enum을 이용해 Singleton을 보장한다.
  2. synchronized 키워드를 메서드에 추가한다.
  3. synchronized 블럭을 lazy 생성 조건문 내부에 추가한다.
  4. 내부 정적 클래스를 구현하여 내부 클래스에서 생성된 인스턴스를 가져온다.

이 방법들 중에서도 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를 보장합니다. 그 이유는 뭘까요?

스크린샷 2022-03-16 오후 4 49 48

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