Ich arbeite seit einigen Jahren täglich mit dem Java Memory Model. Ich denke, ich habe ein gutes Verständnis von dem Konzept der Datenrennen und den verschiedenen Möglichkeiten, sie zu vermeiden (z.B. synchronisierte Blöcke, volatile Variablen usw.). Allerdings gibt es immer noch etwas, das ich nicht vollständig verstehe über das Memory Model, nämlich die Art und Weise, wie finale Felder von Klassen angeblich threadsicher sind, ohne weitere Synchronisierung.
Also gemäß der Spezifikation, wenn ein Objekt ordnungsgemäß initialisiert ist (das heißt, keine Referenz auf das Objekt aus dem Konstruktor entweicht, so dass die Referenz von einem anderen Thread gesehen werden kann), dann wird nach der Konstruktion garantiert, dass jeder Thread, der das Objekt sieht, die Referenzen auf alle finalen Felder des Objekts (im Zustand, in dem sie beim Konstruieren waren) ohne weitere Synchronisierung sieht.
Insbesondere heißt es im Standard (http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4):
Das Nutzungsmodell für finale Felder ist einfach: Setze die finalen Felder für ein Objekt in den Konstruktor dieses Objekts; und schreibe keine Referenz auf das in Konstruktion befindliche Objekt an einen Ort, an dem ein anderer Thread es sehen kann, bevor der Konstruktor des Objekts beendet ist. Wenn dies befolgt wird, wird ein anderer Thread, der das Objekt sieht, immer die richtig konstruierte Version der finalen Felder des Objekts sehen. Er wird auch Versionen von Objekten oder Arrays, auf die diese finalen Felder verweisen, sehen, die mindestens genauso aktuell sind wie die finalen Felder.
Es wird sogar das folgende Beispiel gegeben:
class FinalFieldExample {
final int x;
int y;
static FinalFieldExample f;
public FinalFieldExample() {
x = 3;
y = 4;
}
static void writer() {
f = new FinalFieldExample();
}
static void reader() {
if (f != null) {
int i = f.x; // garantiert 3 zu sehen
int j = f.y; // könnte 0 sehen
}
}
}
Ein Thread A soll "reader()" ausführen und ein Thread B soll "writer()" ausführen.
Bisher scheint alles gut zu sein.
Meine Hauptbedenken haben mit... ist dies in der Praxis wirklich nützlich? Soweit ich weiß, müssen wir, um Thread A (der "reader()" ausführt) die Referenz zu "f" sehen zu lassen, einen Synchronisierungsmechanismus verwenden, wie beispielsweise f als volatile zu kennzeichnen oder Locks zu verwenden, um den Zugriff auf f zu synchronisieren. Wenn wir das nicht tun, ist nicht einmal garantiert, dass "reader()" ein initialisiertes "f" sehen wird, das heißt, da wir keinen synchronisierten Zugriff auf "f" haben, wird der Leser möglicherweise "null" anstelle des vom Writer-Thread konstruierten Objekts sehen. Dieses Problem wird in http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#finalWrong , einer der Hauptreferenzen für das Java Memory Model, erläutert [fette Hervorhebung von mir]:
Nun, nachdem ein Thread ein unveränderliches Objekt konstruiert hat (das heißt, ein Objekt, das nur finale Felder enthält), besteht immer noch typischerweise die Notwendigkeit, Synchronisation zu verwenden, um sicherzustellen, dass es korrekt von allen anderen Threads gesehen wird. Es gibt keine andere Möglichkeit, zum Beispiel sicherzustellen, dass die Referenz auf das unveränderliche Objekt vom zweiten Thread gesehen wird. Die Garantien, die das Programm von finalen Feldern erhält, sollten mit einem tiefen und sorgfältigen Verständnis darüber, wie die Concurrent-Programmierung in Ihrem Code verwaltet wird, sorgfältig abgewogen werden.
Wenn uns nicht einmal garantiert wird, dass die Referenz zu "f" sichtbar ist, und wir daher typische Synchronisierungsmechanismen (volatile, Locks usw.) verwenden müssen, und diese Mechanismen bereits Datenrennen verhindern, ist die Notwendigkeit von final etwas, über das ich nicht einmal nachdenken würde. Ich meine, wenn wir "f" für andere Threads sichtbar machen müssen, müssen wir bereits volatile oder synchronisierte Blöcke verwenden, und diese machen bereits interne Felder für andere Threads sichtbar... worin liegt also der Sinn (in puncto Thread-Sicherheit), ein Feld überhaupt final zu machen?