복붙노트

[SPRING] Spring Framework에서 @PostConstruct로 초기화하면 객체 속성을 휘발성으로 표시해야합니까?

SPRING

Spring Framework에서 @PostConstruct로 초기화하면 객체 속성을 휘발성으로 표시해야합니까?

내가 스프링 싱글 톤 빈 @PostConstruct (단순화 된 코드)에서 초기화를한다고 가정 해보십시오.

@Service
class SomeService {
  public Data someData; // not final, not volatile

  public SomeService() { }

  @PostConstruct
  public void init() {
     someData = new Data(....);
  }
}

다른 bean에 대한 someData 가시성에 대해 걱정해야하고 휘발성으로 표시해야합니까?

(내가 그것을 생성자에서 초기화 할 수 없다고 가정 해보자)

두 번째 시나리오 : @PostConstruct (예 : 명시 적 초기화 또는 생성자에서의 초기화 이후)에서 값을 덮어 쓰면 @PostConstruct에 쓰면이 특성에 처음 쓰지 않습니다.

해결법

  1. ==============================

    1.Spring 프레임 워크는 Java 프로그래밍 언어와 관련이 없으며 프레임 워크 일뿐입니다. 따라서 일반적으로 다른 스레드가 액세스하는 비 최종 필드를 휘발성으로 표시해야합니다. 하루가 끝날 때, 스프링 빈은 자바 객체 일 뿐이고 모든 언어 규칙이 적용됩니다.

    Spring 프레임 워크는 Java 프로그래밍 언어와 관련이 없으며 프레임 워크 일뿐입니다. 따라서 일반적으로 다른 스레드가 액세스하는 비 최종 필드를 휘발성으로 표시해야합니다. 하루가 끝날 때, 스프링 빈은 자바 객체 일 뿐이고 모든 언어 규칙이 적용됩니다.

    최종 필드는 Java 프로그래밍 언어에서 특별한 대우를받습니다. 오라클 성능 담당자 인 Alexander Shipilev는이 문제에 대한 훌륭한 기사를 썼습니다. 간단히 말해서 생성자가 최종 필드를 초기화 할 때 필드 값을 설정하는 어셈블리는 필드가 모든 스레드에서 올바르게 표시되도록하는 추가 메모리 장벽을 추가합니다.

    최종 필드가 아닌 경우 해당 메모리 장벽이 만들어지지 않습니다. 따라서 일반적으로 @ PostConstruct-annotated 메서드가 필드를 초기화 할 수 있으며이 값은 다른 스레드에서 볼 수 없거나 더 나쁘다. 생성자가 부분적으로 만 실행될 때 나타납니다.

    이것은 항상 비 최종 필드를 휘발성으로 표시해야한다는 의미입니까?

    요컨대, 그렇습니다. 필드가 다른 스레드에 의해 액세스 될 수 있다면 그렇게 할 수 있습니다. (Jk1 덕분에) 몇 초 동안 문제를 생각할 때와 똑같은 실수를하지 말고 Java 코드의 실행 순서를 생각해보십시오. Spring 애플리케이션 컨텍스트가 단일 스레드에서 부트 스트래핑된다고 생각할 수도있다. 이는 부트 스트랩 스레드가 비 휘발성 필드에 문제가 없음을 의미합니다. 따라서 응용 프로그램 컨텍스트가 완전히 초기화되거나 주석이 달린 메서드가 호출 될 때까지 다른 스레드에 응용 프로그램 컨텍스트를 노출시키지 않는 한 모든 것이 순서대로 있다고 생각할 수 있습니다. 이렇게 생각하면,이 스레드 이후에 필드를 변경하지 않는 한 다른 스레드가 잘못된 필드 값을 캐시 할 수있는 기회가 없다고 가정 할 수 있습니다.

    반대로, 컴파일 된 코드는 명령어를 재정렬 할 수 있습니다. 즉, 관련 Bean이 Java 코드의 다른 스레드에 노출되기 전에 @ PostConstruct-annotated 메소드가 호출 되더라도,이 happen-before 관계가 반드시 컴파일 된 시점에서 유지되지는 않습니다 런타임시 코드. 따라서, 다른 스레드는 비 휘발성 필드를 읽거나 캐시 할 수 있지만 아직 초기화되지 않았거나 부분적으로 초기화되지 않은 상태 여야합니다. 이것은 미묘한 버그를 유발할 수 있으며 Spring 문서는 불행히도이 경고를 언급하지 않습니다. 이러한 JMM의 세부 사항은 개인적으로 최종 필드 및 생성자 주입을 선호하는 이유입니다.

    업데이트 : 다른 질문에서이 답변에 따르면, 휘발성으로 필드를 표시하지 않는 것이 유효한 결과를 산출하는 시나리오가 있습니다. 이 부분을 좀 더 자세히 조사해 봄 프레임 워크는 사실상 일정한 양의 - 안전성을 보장하기 전에 -을 보장합니다. 일찍이 관계가있는 JLS를 살펴보십시오.

    Spring 프레임 워크는 이것을 이용한다. 모든 빈은 단일 맵에 저장되고, 빈이 맵에 등록되거나 검색 될 때마다 특정 모니터가 획득됩니다. 결과적으로 완전히 초기화 된 bean을 등록한 후에 동일한 모니터가 잠금 해제되고 다른 스레드에서 동일한 bean을 검색하기 전에 잠긴다. 이로 인해이 다른 스레드는 Java 코드의 실행 순서에 반영되는 동시 발생 관계를 준수하게됩니다. 따라서 빈을 한 번 부트 스트랩하면 완전히 초기화 된 빈에 액세스하는 모든 스레드는 표준 방식 (즉, 응용 프로그램 컨텍스트를 쿼리하거나 자동 작성으로 명시 적 검색)으로 빈에 액세스하는 동안이 상태를 볼 수 있습니다. 이것은 예를 들어 setter 주입이나 @PostConstruct 메소드의 사용을 필드 volatile을 선언하지 않고도 안전하게합니다. 사실, 루프 내의 필드에 액세스 할 때 고통 스러울 수있는 것과 키워드가 잘못된 의도를 나타 내기 때문에 각 읽기에 대해 런타임 오버 헤드가 발생하므로 휘발성 필드를 피해야합니다. Akka 프레임 워크는 스프링 이외의 Akka가 문제에 대한 몇 가지 문제를 해결하는 유사한 전략을 적용합니다.

    그러나이 보증은 부트 스트랩 이후에 Bean을 검색 할 때만 제공됩니다. 부트 스트랩 이후에 비 휘발성 필드를 변경하거나 초기화 중에 빈 참조를 유출 한 경우이 보증은 더 이상 적용되지 않습니다.

    이 기능에 대해 자세히 설명하는이 오래된 블로그 항목을 확인하십시오. 분명히,이 기능은 Spring 사람들도 알고 있지만 (오랜 시간 동안 아무 것도하지 않았 음) 문서화되지 않았습니다.

  2. ==============================

    2.당신이 왜해서는 안되는지 알지 못합니다. Spring 프레임 워크는 @PostConstruct를 호출 할 때 추가적인 스레드 안전 보장을 제공하지 않으므로 일반적인 가시성 문제가 여전히 발생할 수 있습니다. 일반적인 방법은 someData final을 선언하는 것이지만 필드를 여러 번 수정하려는 경우에는 분명히 적합하지 않습니다.

    당신이 왜해서는 안되는지 알지 못합니다. Spring 프레임 워크는 @PostConstruct를 호출 할 때 추가적인 스레드 안전 보장을 제공하지 않으므로 일반적인 가시성 문제가 여전히 발생할 수 있습니다. 일반적인 방법은 someData final을 선언하는 것이지만 필드를 여러 번 수정하려는 경우에는 분명히 적합하지 않습니다.

    필드에 처음 쓰는 것인지 아닌지는 중요하지 않습니다. Java Memory Model에 따르면 재정렬 / 가시성 문제는 두 경우 모두에 적용됩니다. 유일한 예외는 최종 필드에 대해 처음으로 안전하게 작성 될 수 있지만 나중의 지정 (예 : 리플렉션을 통해)은 표시되지 않을 수 있습니다.

    그러나 휘발성은 다른 스레드에서 필요한 가시성을 보장 할 수 있습니다. 또한 부분적으로 구성된 Data 객체의 원치 않는 노출을 방지합니다. 재정렬 문제로 인해 someData 참조는 생성자 연산과 기본값 할당을 포함하여 모든 필요한 객체 생성 작업이 완료되기 전에 할당 될 수 있습니다.

    업데이트 : @raphw가 작성한 포괄적 인 연구에 따르면 Spring은 모니터가 보호하는 맵에서 싱글 톤 빈을 저장합니다. 이것은 사실입니다. org.springframework.beans.factory.support.DefaultSingletonBeanRegistry의 소스 코드에서 알 수 있습니다.

    public Object getSingleton(String beanName, ObjectFactory singletonFactory) {
        Assert.notNull(beanName, "'beanName' must not be null");
        synchronized (this.singletonObjects) {
            Object singletonObject = this.singletonObjects.get(beanName);
            ...
            return (singletonObject != NULL_OBJECT ? singletonObject : null);
        }
    }
    

    이것은 @PostConstruct에 thread-safety 프로퍼티를 제공 할 수 있습니다 만, 다음과 같은 이유로 충분한 보증이라고 생각하지 않습니다 :

    참고 사항 : 위의 모든 내용은 콩이 멀티 스레드 액세스의 대상이되는 경우에 해당됩니다. 프로토 타입 범위 Bean의 경우 명시 적으로 여러 스레드에 노출되지 않으면 실제로 그렇지 않습니다. 싱글 톤 범위의 빈에 주입한다.

  3. ==============================

    3.질문에 정의 된대로 SomeService 클래스는 스레드로부터 안전하지 않습니다. 클래스를 스레드로부터 안전하게 만들려면 다음을 수행해야합니다.

    질문에 정의 된대로 SomeService 클래스는 스레드로부터 안전하지 않습니다. 클래스를 스레드로부터 안전하게 만들려면 다음을 수행해야합니다.

    someData 필드가 휘발성이 아니고 조건 2와 3이 충족되면 클래스의 제어 범위를 벗어나는 다른 조건이 충족되는 경우에만 클래스가 아닌 스레드 안전성을 유지할 수 있습니다. 가장 안전한 방법은 클래스를 스레드로부터 안전하게 만드는 것입니다. 그런 다음 모든 상황에서 클래스를 사용할 수 있으며 항상 스레드로부터 안전하다는 것이 보장됩니다.

    숙고해야 할 몇 가지 사항 :

    Spring은 멋진 프레임 워크이지만, 큰 결함 중 하나는 문서가 스레드 안전성 문제를 다루는 경우는 거의 없다는 점입니다.

  4. from https://stackoverflow.com/questions/23906808/should-i-mark-object-attributes-as-volatile-if-i-init-them-in-postconstruct-in by cc-by-sa and MIT license