이해하는 것은 왜 자바 메모리 모델의 관점에서 생성자 내부 스레드를 시작하는 데 안전하지 않은

케니 웡 :

에 따르면, 연습 자바 동시성 , 클래스 생성자 내에서 스레드를 시작하는 것은 위험합니다. 그 이유는 이것이 노출이다 this오브젝트가 완전히 구축되기 전에 다른 스레드에 포인터를.

이 주제는 많은 이전 StackOverflow의 질문에 설명에도 불구하고이 같은 우려 이유, 난 여전히 어려움을 이해하는 데. 특히, 나는 생성자 내부 스레드를 시작하는 자바 메모리 모델의 관점에서 메모리 일관성 문제를 야기 할 수 있는지 여부에 대한 명확성을 추구하도록하겠습니다.

내가 당신에게 내가 원하는 물건의 종류의 구체적인 예를 들어 보겠습니다. (수 (20)가 콘솔에 인쇄하는 코드의 부분에 대해 원하는 출력된다.)

private static class ValueHolder {

    private int value;
    private Thread thread;

    ValueHolder() {
        this.value = 10;
        thread = new Thread(new DoublingTask(this)); // exposing "this" pointer!!!
        thread.start();   // starting thread inside constructor!!!
    }

    int getValue() {
        return value;
    }

    void awaitTermination() {
        try {
            thread.join();
        } catch (InterruptedException ex) {}
    }

}

private static class DoublingTask implements Runnable {

    private ValueHolder valueHolder;

    DoublingTask(ValueHolder valueHolder) {
        this.valueHolder = valueHolder;
    }

    public void run() {
        System.out.println(Thread.currentThread().getName());
        System.out.println(valueHolder.getValue() * 2);  // I expect to print out 20...
    }

}

public static void main(String[] args) {
    ValueHolder myValueHolder = new ValueHolder();
    myValueHolder.awaitTermination();
}

예, 우리는 생성자에서 반환하기 전에 스레드가 시작되었는지 알고있다. 네, 알고 this포인터가 스레드에 노출되어있다. 그럼에도 불구하고, 나는 코드가 정확한지 확신합니다. 나는 항상 콘솔에 숫자 20을 인쇄 할 전망이다.

  • 할당은 this.value = 10 발생-전에 thread.start() . (이 있기 때문입니다 this.value = 10선행 thread.start()프로그램 순서입니다.)
  • 의 호출 thread.start()메인 스레드에서가 발생-전에 의 시작 .run()새로 만든 스레드 방법. (이므로 thread.start()동기 동작이다.)
  • 의 시작 .run()방법은 발생-전에System.out.println(valueHolder.getValue() * 2); 인쇄 문. (또 다시, 프로그램 순서에 의해).

따라서, 자바 메모리 모델에 의해, 인쇄 문의 제대로-초기화 된 값을 읽어야합니다 valueHolder.value(즉, 10). 의 조언을 무시에도 불구 그래서 연습 자바 동시성을 , 난 여전히 코드의 올바른 조각을 쓴 것으로 보인다.

내가 실수를 한 적이 있습니까? 나는 무엇을 놓치고?


UPDATE : 답변 및 의견을 바탕으로, 나는 지금 내 코드 예제는 이해 이다 내가 제공 한 이유 기능적으로 정확한. 다른 개발자가 미래에 스레드의 시작 후에 더 초기화 문을 추가 것이라는 가능성이 있기 때문에,이 방법으로 코드를 작성 나쁜 관행이다. 이 클래스의 서브 클래스를 구현할 때 이러한 오류 가능성이있는 한 상황이다.

마이클 :

나는 당신의 클래스를 서브 클래스 화해하자. 그것은 그들이 필요한 시간의 필드를 초기화하지 않았을 수 있습니다.

class BetterValueHolder extends ValueHolder
{
    private int betterValue;

    BetterValueHolder(final int betterValue)
    {
        // not actually required, it's added implicitly anyway.
        // just to demonstrate where your constructor is called from
        super();

        try
        {
            Thread.sleep(1000); // just to demonstrate the race condition more clearly
        }
        catch (InterruptedException e) {}

        this.betterValue = betterValue;
    }

    @Override
    public int getValue()
    {
        return betterValue;
    }
}

이 관계의 생성자에 주어진 어떤 값의 제로를 출력합니다 BetterValueHolder.

추천

출처http://43.154.161.224:23101/article/api/json?id=172959&siteId=1