[Effective-Java] Chapter11 #83. 지연 초기화는 신중히 사용하라

2023. 8. 13. 14:23·Reference/Effective-Java
⚔️ 지연 초기화는 양날의 검
소프트웨어 시스템은 Application 객체를 제작하고 의존성을 서로 연결하는 준비 과정과
준비 과정 이후에 이어지는 Runtime 로직을 분리해야 한다.

 

제작(Construction)과 사용(use)은 아주 다르다.

시작 단계는 모든 Application이 풀어야 할 관심사(Concern)다.

 

지연 초기화는 관심사 분리가 제대로 이루어지지 않는 좀스러운 방법 중 하나다.

public Service getService() {
    if (service == null)
        service = new MyServiceImpl();
    return service;
}
  • 장점
    1. 실제 필요한 시점에 객체를 생성하므로 불필요한 부하를 줄인다.
      • Class와 Instance 초기화 때 발생하는 순환 문제를 해결하는 효과도 있다.
    2. Application 실행 속도가 빨라진다.
    3. 어떠한 경우에도 null 포인터를 반환하지 않는다.
  • 단점
    1. getSerivce() 메서드가 MyServiceImpl과 생성자 인수에 명시적으로 의존한다.
      • Runtime 로직에서 MyServiceImpl 객체를 전혀 사용하지 않더라도 의존성을 해결하기 전까지 컴파일이 안 된다.
    2. 테스트 관점에서 비효율적이다.
      • MyServiceImple에 대한 테스트 전용 객체(Test Double 혹은 Mock Object)를 service 필드에 할당해야 한다.
    3. 일반 Runtime 로직에 객체 생성 로직을 섞어놔서 모든 실행 경로를 테스트해야 한다.
      • service에 대한 null check를 Runtime 과정에서 수행해야 한다.
      • 작게나마 SRP를 위배한다고 볼 수 있다.
    4. MyServiceImpl이 모든 상황에 적합한 객체일 것이라 보장할 수 없다.
    5. 지연 초기화하는 필드에 접근하는 비용이 커진다.
      • 초기화가 이루어지는 비율, 실제 초기화에 드는 비용, 초기화된 필드를 호출하는 빈도수에 따라 성능 저하 원인이 될 수 있다.
💡 기본적으로 설정 논리는 일반 실행 논리와 분리해야 모듈성이 높아진다.

 

📌 초기화 전략

아래 전략들은 기본 타입 필드와 객체 참조 필드에 모두 적용할 수 있으면서, Thread-safe 하다.

 

1️⃣ 대부분의 상황에서 일반적인 초기화가 지연 초기화보다 낫다

class Foo {
    private final FieldType field = computeFieldValue();  
    
    private FieldType computeFieldValue() {
        return new FieldType();
    }
}
  • 인스턴스 필드를 선언할 때 수행하는 일반적인 초기화 모습
  • Item 17의 조언에 따라 불변식을 지키고 있다.

 

2️⃣ 지연 초기화로 초기화 순환성을 깨뜨리고 싶다면 synchronized를 사용하라

private FieldType field;

public synchronized FieldType getField() {
    if (field == null)
        field = computeFieldValue();
    return field;
}
  • 위 관용구는 static 필드에도 똑같이 적용된다. (물론 필드와 접근자 메서드에 static 한정자를 추가해야 한다.)

(책에는 "초기화 순환성을 깨뜨리게 된다면"이라고 적혀있는데, 원문을 확인해보면 "깨뜨리고 싶다면"이 더 알맞는 해석이라 생각해서 고쳤다.)

 

✒️ 초기화 순환성(Initialization Circularity)

클래스 A → B → C → A 구조로 초기화 순환성이 존재할 경우, A객체 하나만 생성해도 StackOverFlow가 발생한다.
각 클래스에 Lazy Initialization을 적용하면, 해당 객체가 사용될 때만 초기화 되므로 순환성을 깨뜨릴 수 있다.

다만, 클래스가 로딩되는 순서에 따라 초기화 순서가 불명확하고, 이에 따라 클래스 변수 참조 순서 또한 불명확한데 synchronized 지연 초기화를 사용하면 문제를 없앨 수 있다...는 거 같은데 솔직히 이해가 안 간다.

 

3️⃣ 성능 때문에 정적 필드를 지연 초기화 할 때는 지연 초기화 홀더 클래스 관용구를 사용하라

private static class FieldHolder {
    static final FieldType field = computeFieldValue();
}

public static FieldType getField() { return FieldHolder.field; }

 

  • getField()가 처음 호출되는 순간 FieldHolder.field가 처음 읽히면서, FieldHolder 클래스 초기화를 촉발한다.
  • getField()메서드가 필드에 접근하면서 동기화를 전혀 하지 않으므로 성능이 느려질 거리가 없다.
  • 일반적인 VM은 오직 클래스 초기화 단계에서만 필드 접근 동기화를 걸고, 그 이후에는 동기화 코드를 제거하여 아무런 검사나 동기화 없이 필드에 접근하게 된다.

 

4️⃣ 성능 때문에 인스턴스 필드를 지연 초기화 할 때는 이중검사 관용구를 사용하라

private volatile FieldType field;

public FieldType getField() {
    FieldType result = field;
    if (result != null) // 첫 번째 검사 (Lock 사용 안 함)
        return result;
    
    synchronized(this) {
        if (field == null) // 두 번째 검사 (Lock 사용)
            field = computeFieldValue();
        return field;
    }
}
  • 필드 값을 두 번 검사한다.
    1. 동기화 없이 검사
      • 속도 향상 목적
      • 매번 초기화하기 보다, 처음 필드에 접근하는 시점에만 초기화하는 것이 좋다.
      • 따라서 이미 초기화된 경우 추가적인 동기화 없이 필드에 접근할 수 있다.
    2. (필드가 아직 초기화되지 않았다면) 동기화하여 검사
      • Thread-safe 목적
      • Multi-thread 환경에서 동시 초기화하는 상황을 방지한다.
  • 지연 초기화하려는 인스턴스 필드는 반드시 volatile로 선언하라
    • 동기화 없이 검사 단계 때문에, 필드가 한 번 초기화되면 동기화 과정이 없다.
    • 따라서 volatile로 선언하여 값의 최신 상태를 보장할 수 있다.
  • 반드시 필요하진 않지만 성능을 높여주고, 저수준 동시성 프로그래밍에 표준적으로 적용된다.
  • 정적 필드에도 적용할 수 있지만 그럴 필요가 없고, 지연 초기화 홀더 클래스 방식이 더 낫다.

 

🧟 변종. 단일검사 관용구

private volatile FieldType field;

public FieldType getField() {
    FieldType result = field;
    if (result == null)
        field = result = computeFieldValue();
    retrn result;
}
  • 반복해서 초기화를 해도 상관없는 인스턴스 필드를 지연 초기화하는 경우, 두 번째 검사를 생략할 수 있다.
  • 기본 타입 필드에 이중검사/단일검사 관용구 적용 시, 필드의 값을 null대신 0과 비교하면 된다.
  • 특정한 조건에서 단일 검사 필드의 volatile 한정자를 제거해도 된다.
    • 필드 타입이 long과 double을 제외한 다른 기본 타입인 경우
    • 모든 Thread가 field의 값을 다시 계산해도 상관 없는 경우
    • 짜릿한 단일검사(racy single-check) 관용구라 불린다.
      • 특정 환경에선 필드 접근 속도를 높여주지만, 초기화가 Thread 당 최대 한 번 더 수행될 수 있다.
      • 보통은 거의 쓰지 않는 기법이다.
저작자표시 비영리 (새창열림)
'Reference/Effective-Java' 카테고리의 다른 글
  • [Effective-Java] Chapter12 #85. 자바 직렬화의 대안을 찾으라
  • [Effective-Java] Chapter11 #84. 프로그램의 동작을 스레드 스케줄러에 기대지 말라
  • [Effective-Java] Chapter11 #82. 스레드 안정성 수준을 문서화하라
  • [Effective-Java] Chapter11 #81. wait와 notify보다는 동시성 유틸리티를 애용하라
나죽못고나강뿐
나죽못고나강뿐
싱클레어, 대부분의 사람들이 가는 길은 쉽고, 우리가 가는 길은 어려워요. 우리 함께 이 길을 가봅시다.
  • 나죽못고나강뿐
    코드를 찢다
    나죽못고나강뿐
  • 전체
    오늘
    어제
    • 분류 전체보기 (483)
      • Computer Science (60)
        • Git & Github (4)
        • Network (17)
        • Computer Structure & OS (13)
        • Software Engineering (5)
        • Database (9)
        • Security (5)
        • Concept (7)
      • Frontend (22)
        • React (14)
        • Android (4)
        • iOS (4)
      • Backend (85)
        • Spring Boot & JPA (53)
        • Django REST Framework (14)
        • MySQL (10)
        • Nginx (1)
        • FastAPI (4)
        • kotlin (2)
        • OpenSearch (1)
      • DevOps (24)
        • Docker & Kubernetes (11)
        • Naver Cloud Platform (1)
        • AWS (2)
        • Linux (6)
        • Jenkins (0)
        • GoCD (3)
      • Coding Test (112)
        • Solution (104)
        • Algorithm (7)
        • Data structure (0)
      • Reference (139)
        • Effective-Java (90)
        • Pragmatic Programmer (0)
        • CleanCode (11)
        • Clean Architecture (5)
        • Test-Driven Development (4)
        • Relational Data Modeling No.. (0)
        • Microservice Architecture (2)
        • 알고리즘 문제 해결 전략 (9)
        • Modern Java in Action (0)
        • Spring in Action (0)
        • DDD start (0)
        • Design Pattern (6)
        • 대규모 시스템 설계 (7)
        • JVM 밑바닥까지 파헤치기 (4)
        • The Pragmatic Programmer (1)
      • Service Planning (2)
      • Side Project (5)
      • AI (1)
      • MATLAB & Math Concept & Pro.. (2)
      • Review (24)
      • Interview (4)
      • IT News (3)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • 깃
  • 공지사항

    • 요새 하고 있는 것
    • 한동안 포스팅은 어려울 것 같습니다. 🥲
    • N Tech Service 풀스택 신입 개발자가 되었습니다⋯
    • 취업 전 계획 재조정
    • 취업 전까지 공부 계획
  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.2
나죽못고나강뿐
[Effective-Java] Chapter11 #83. 지연 초기화는 신중히 사용하라
상단으로

티스토리툴바