О ссылке на объект перед конструктором объекта завершена
Каждый из вас знает об этой функции JMM, которая иногда ссылается на объект, может получать значение до завершения конструктора этого объекта.
В JLS7, p. 17.5 окончательную полевую семантику мы также можем прочитать:
Модель использования для полей final
проста: установите поля final
для объекта в этом объектном конструкторе; и не пишите ссылка на объект, который строится в месте, где поток может видеть его до завершения конструктора объекта. Если это, тогда, когда объект рассматривается другим потоком, это нить всегда будет видеть правильно построенную версию этого object final
. (1)
И только после этого в JLS следует пример, демонстрирующий, как не может быть инициализировано поле non-final (1 пример 17.5-1.1) (2)
:
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; // guaranteed to see 3
int j = f.y; // could see 0
}
}
}
Кроме того, в этом вопросе-ответе г-н Грей написал:
Если вы помечаете это поле как final
, то конструктору гарантируется завершите инициализацию как часть конструктора. В противном случае вы перед его использованием необходимо синхронизировать блокировку. (3)
Итак, вопрос:
1) В соответствии с утверждением (1) нам следует избегать ссылки на неизменяемый объект до завершения его конструктора
2) Согласно приведенному JLS примеру (2) и выводу (3), мы можем смело обмениваться ссылкой на неизменный объект до, его конструктор закончен, т.е. когда все его поля final
.
Нет ли противоречия?
EDIT-1. То, что я точно имею в виду. Если мы будем модифицировать класс таким образом, это поле y
будет также final
(2):
class FinalFieldExample {
final int x;
final int y;
...
следовательно, в reader()
будет гарантировано, что:
if (f != null) {
int i = f.x; // guaranteed to see 3
int j = f.y; // guaranteed to see 4, isn't it???
Если да, почему мы должны избегать ссылки на объект f
до того, как он завершит конструкцию (согласно (1)), когда все поля f
являются окончательными?
Ответы
Ответ 1
Нет ли противоречия [в JLS вокруг конструкторов и публикации объектов]?
Я считаю, что это несколько разные проблемы, которые не противоречат друг другу.
Ссылка JLS берет на себя хранение ссылки на объект в месте, где другие потоки могут видеть его до завершения конструктора. Например, в конструкторе вы не должны помещать объект в поле static
, которое используется другими потоками, и не должно выставлять поток.
public class FinalFieldExample {
public FinalFieldExample() {
...
// very bad idea because the constructor may not have finished
FinalFieldExample.f = this;
...
}
}
Вы также не должны запускать поток в конструкторе:
// obviously we should implement Runnable here
public class MyThread extends Thread {
public MyThread() {
...
// very bad idea because the constructor may not have finished
this.start();
}
}
Даже если все ваши поля final
в классе, разделение ссылки на объект на другой поток до завершения конструктора не может гарантировать, что поля были установлены к тому времени, когда другие потоки начнут использовать объект.
В моем ответе речь шла об использовании объекта без синхронизации после завершения конструктора. Это немного другой вопрос, хотя и схожий с конструкторами, отсутствие синхронизации и переупорядочение операций компилятором.
В JLS 17.5-1 они не назначают статическое поле внутри конструктора. Они ставят статическое поле в другом статическом методе:
static void writer() {
f = new FinalFieldExample();
}
Это критическое различие.
Ответ 2
В полном примере
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; // guaranteed to see 3
int j = f.y; // could see 0
}
}
}
Как вы можете видеть, f
не устанавливается, пока не вернется конструктор. Это означает, что f.x
является безопасным, потому что он final
И конструктор вернулся.
В следующем примере ни одно значение не должно быть установлено.
class FinalFieldExample {
final int x;
int y;
static FinalFieldExample f;
public FinalFieldExample() {
x = 3;
y = 4;
f = this; // assign before finished.
}
static void writer() {
new FinalFieldExample();
}
static void reader() {
if (f != null) {
int i = f.x; // not guaranteed to see 3
int j = f.y; // could see 0
}
}
}
В соответствии с утверждением (1) нам следует избегать ссылки на неизменяемый объект до завершения его конструктора
Вы не должны позволять ссылаться на объект, прежде чем он будет создан по целому ряду причин (неизменяемый или другой мудрый), например. объект может выбросить исключение после сохранения объекта.
Согласно приведенному JLS примеру (2) и выводу (3), мы можем смело ссылаться на неизменяемый объект, т.е. когда все его поля являются окончательными.
Вы можете безопасно обмениваться ссылкой на неизменяемый объект между потоками после того, как объект был создан.
Примечание: вы можете увидеть значение неизменяемого поля до того, как оно будет установлено в методе, вызываемом конструктором.
Ответ 3
Конструкция выхода играет здесь важную роль; JLS говорит, что "действие замораживания в последнем поле f of o происходит, когда c выходит из". Публикация ссылки до/после выхода конструктора сильно отличается.
неформально
1 constructor enter{
2 assign final field
3 publish this
4 }constructor exit
5 publish the newly constructed object
[2] не может быть переупорядочен за пределами выхода из конструктора. поэтому [2] нельзя переупорядочить после [5].
но [2] можно переупорядочить после [3].
Ответ 4
Заявление 1) не говорит, что вы думаете. Во всяком случае, я бы перефразировал ваше утверждение:
1) Согласно заявлению (1), нам следует избегать ссылки на неизменяемый объект до завершения его конструктора
читать
1) Согласно заявлению (1), нам следует избегать ссылки на измененный объект до завершения его конструктора
где то, что я имею в виду под mutable, - это объект, у которого есть ЛЮБЫЕ необязательные поля или конечные ссылки на изменяемые объекты. (должен признать, что я не на 100%, что вам нужно беспокоиться о конечных ссылках на изменяемые объекты, но я думаю, что я прав...)
Другими словами, вы должны различать:
- конечные поля (неизменяемые части возможно неизменяемого объекта)
- нефинализированные поля, которые должны быть инициализированы до того, как кто-либо взаимодействует с этим объектом.
- нефинальные поля, которые не нужно инициализировать до того, как кто-либо взаимодействует с этим объектом.
Вторая проблема - это проблема.
Таким образом, вы можете обмениваться ссылками на неизменяемые объекты (все поля final
), но вам нужно соблюдать осторожность с объектами, которые имеют не final
поля, которые должны быть инициализированы до того, как объект может быть использован кем-либо.
Другими словами, для отредактированного примера JLS вы указали, где оба поля final
, int j = f.y;
гарантированно будет окончательным. Но это означает, что вам НЕ нужно избегать ссылки на объект f, потому что он всегда будет в правильном состоянии, прежде чем кто-нибудь сможет его увидеть. Вам не нужно беспокоиться об этом, JVM делает.