필드를 선언하는 경우 컴파일 타임 오류로 필드를 수정하거나 초기화하지 않는 것이 좋습니다.
멀티 스레드 코드에서 클래스 A
의 인스턴스를 데이터 레이스 (즉, 동기화하지 않고, 즉 정적 필드와 같이 전역 적으로 사용 가능한 위치에 저장)와 공유하는 경우 일부 스레드는 getA()
값을 변경합니다!
Final
필드는 동기화 없이도 생성자가 완료된 후에 모든 스레드에서 해당 값을 볼 수 있도록 보장됩니다 (JVM specs).
이 두 클래스를 고려
final class A {
private final int x;
A(int x) { this.x = x; }
public getX() { return x; }
}
final class B {
private int x;
B(int x) { this.x = x; }
public getX() { return x; }
}
모두 A
및 B
는 초기화 경기장 x
의 값을 수정할 수 없습니다 의미에서 불변 (의 반사 잊어 보자). 유일한 차이점은 x
필드에 A가 final
으로 표시되어 있다는 것입니다.이 작은 차이가 큰 영향을 줄 것입니다. 이 추측은 사소한 보일 수 있지만,
class Main {
static A a = null;
static B b = null;
public static void main(String[] args) {
new Thread(new Runnable() { void run() { try {
while (a == null) Thread.sleep(50);
System.out.println(a.getX()); } catch (Throwable t) {}
}}).start()
new Thread(new Runnable() { void run() { try {
while (b == null) Thread.sleep(50);
System.out.println(b.getX()); } catch (Throwable t) {}
}}).start()
a = new A(1); b = new B(1);
}
}
두 스레드가보고있는 필드는 점에 유의 (주 스레드를 설정 한 후 null이 아닌 것을 볼 일이 가정 :
이제 다음 코드를 고려 그것은 JVM에 의해 보장되지 않습니다!). 이 경우
, 우리는
x
필드가 마지막이기 때문에
a
시계 스레드가 값
1
를 인쇄 할 것이라는 점을 확신 할 수 있습니다 - 생성자가 완료되면 그래서, 그것은이 보장되는 객체를 참조하는 모든 스레드
x
에 대한 올바른 값이 표시됩니다.
그러나 다른 스레드가 수행 할 작업에 대해서는 확신 할 수 없습니다. 사양은 0
또는 1
중 하나를 인쇄 할 수 있음을 보증합니다. 필드가 final
이 아니기 때문에 어떤 종류의 동기화 (synchronized
또는 volatile
)도 사용하지 않았 으면 스레드는 초기화되지 않은 필드를보고 0을 인쇄합니다. 다른 가능성은 실제로 필드가 초기화 된 것을보고 인쇄합니다. 1. 다른 값을 인쇄 할 수 없습니다.
또한
, 어떤 발생할 수있는 것은 당신이 읽고 b
의 getX()
의 값을 인쇄 유지하는 경우, 그것은 0
을 인쇄 잠시 후 1
인쇄를 시작할 수 있다는 것입니다! 이 경우 두 번째 스레드의 관점에서 볼 때 : 필드가 있어야하는 이유는 명확합니다. b
은 변경자를 제공하지 않아도 변경할 수없는 경우에도 변경되었습니다.
당신이 필드 final
하지 않고 두 번째 스레드가 x
에 대한 올바른 가치를 볼 것이라는 점을 보장하려는 경우, 당신은 휘발성 B
의 인스턴스 보유하고있는 필드 선언 할 수 :
이
class Main {
// ...
volatile static B b;
// ...
}
다른 가능성이다
final class B {
private int x;
private synchronized setX(int x) { this.x = x; }
public synchronized getX() { return x; }
B(int x) { setX(x); }
}
또는 홈페이지의 코드를 수정할 때 F로 동기화를 가산함으로써 : 어느 클래스 B를 변경하여 설정하고, 필드를 판독 할 때 때 동기화 b
이 읽혀지고 쓰여질 때 두 작업이 동일한 객체에서 동기화되어야합니다!
가장 시원하고 안정적이며 성능이 우수한 솔루션은 필드를 x
결승으로 만드는 것입니다. 최종 참고로
,
최종 모든 필드를 가지고 불변, 스레드 안전 클래스 절대적으로 필요하지 않습니다.그러나 이러한 클래스 (스레드 안전, 변경 불가능, 최종 필드가 포함되지 않음)는주의 깊게 설계해야하며 전문가를 위해 남겨 두어야합니다.
예를 들어 java.lang.String 클래스가 있습니다. 그것은 마지막이 아닌 private int hash;
필드를 가지고 있으며, 해시 코드()에 대한 캐시로 사용됩니다
private int hash;
public int hashCode() {
int h = hash;
int len = count;
if (h == 0 && len > 0) {
int off = offset;
char val[] = value;
for (int i = 0; i < len; i++)
h = 31*h + val[off++];
hash = h;
}
return h;
}
당신이 볼 수 있듯이, 해시 코드() 메소드는 먼저 (비 최종)를 읽고 필드 hash
. 초기화되지 않은 경우 (즉, 0 인 경우) 값을 다시 계산하고 설정합니다. 해시 코드를 계산하고 필드에 쓰여진 스레드의 경우이 값은 영원히 유지됩니다.
그러나 스레드가 다른 것으로 설정 한 후에도 다른 스레드는 여전히 해당 필드에 대해 0을 볼 수 있습니다. 이 경우 다른 스레드는 해시를 다시 계산하고 과 동일한 값을 얻은 다음 설정합니다.
여기서 클래스의 불변성과 스레드 안전성을 정당화한다는 것은 모든 스레드가 비 최종 필드에 캐시 된 경우에도 hashCode()에 대해 정확히 동일한 값을 얻게된다는 것입니다. 이는 다시 계산되고 정확한 값을 얻을 수 있습니다.
이 모든 추론은 매우 미묘하기 때문에 은 모든 필드가 스레드로부터 안전하지 않은 클래스에서 최종으로 표시되는 것이 좋습니다.
권위에 의문을 던지는 것이 좋지만 여기서는 조슈아 블로치에 대해 이야기하고 있습니다. – duffymo
불변 클래스 란 무엇을 의미합니까? 인스턴스가 변경 가능하지 않아야하거나이 유형으로 형변환 될 수있는 모든 객체 (예 : 하위 클래스에서 인스턴스화 된 객체)를 의미합니까? 두 번째 경우에는 설정할 수있는 새 속성 b가있는 하위 클래스 B를 만드는 것으로 충분합니다. 그러면 B의 인스턴스가 변경 될 수 있습니다. –
Brian Goetz의 * Java Concurrency in Practice *를 권하고 싶습니다. 3 장 (또는 4?)을 읽고 나면, 불변 인 thread-safe 오브젝트의 필드가 왜 최종적인 것인지 분명해진다. 미리보기로서 아래의 내 대답을 참조하십시오. –