에 따르면, 연습 자바 동시성 , 클래스 생성자 내에서 스레드를 시작하는 것은 위험합니다. 그 이유는 이것이 노출이다 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
.