О ссылке на объект до завершения конструктора объекта

Каждый из вас знает об этой особенности JMM, что иногда ссылка на объект может получить значение до завершения конструктора этого объекта.

В JLS7, стр. 17.5 заключительная семантика поля мы также можем прочитать:

Модель использования final полей проста: установите final поля для объекта в конструкторе этого объекта; и не записывайте ссылку на создаваемый объект в месте, где другой поток может видеть его до того, как конструктор объекта будет завершен. Если это будет выполнено, то, когда объект будет виден другим потоком, этот поток всегда будет видеть правильно сконструированную версию final полей этого объекта. (1)

И сразу после этого в JLS следует пример, демонстрирующий, как не гарантируется инициализация неокончательного поля (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 являются окончательными?


person Andremoniy    schedule 01.02.2013    source источник
comment
см. также: Семантика конечных полей в потоках   -  person gnat    schedule 06.12.2013


Ответы (4)


Нет ли противоречия [в 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();
}

Это критическая разница.

person Gray    schedule 01.02.2013
comment
Итак, вы написали: Например, вы не должны помещать объект в статическое поле, которое используется другими потоками. Взгляните на Example 17.5-1. из JLS7. Они делают именно это: static FinalFieldExample f; ... f = new FinalFieldExample(); и гарантируют, что final f.x будет правильно инициализирован. - person Andremoniy; 01.02.2013
comment
Значит, они устанавливают эту статику из внутри конструктора @Andremoniy? Вот в чем проблема. - person Gray; 01.02.2013
comment
они делают это каким-то статическим методом: static void writer() { f = new FinalFieldExample(); }. - person Andremoniy; 01.02.2013
comment
Верно. Это отличается от установки статического поля из внутри конструктора FinalFieldExample. Вот о чем я говорю @Andremoniy. - person Gray; 02.02.2013
comment
Извините, но я все еще не понимаю. Вы написали: Даже если все ваши поля являются final в классе, совместное использование ссылки на объект в другом потоке до завершения конструктора не может гарантировать, что окончательные поля были установлены к тому времени, когда другие потоки начнут использовать объект . Но в примере 17.5-1. они действительно разделяют ссылку на объект между потоками до того, как этот конструктор будет завершен. Почему? - person Andremoniy; 02.02.2013
comment
Нет, они не @Andremoniy. Они назначают статическое поле в другом статическом методе. Я отредактировал свой ответ. - person Gray; 02.02.2013
comment
позвольте нам продолжить обсуждение в чате - person Andremoniy; 02.02.2013
comment
@Gray: В спецификации JLS записана еще одна строка: Поскольку метод записи записывает f после завершения конструктора объекта, метод чтения будет гарантированно видеть правильно инициализированное значение для f. Мой вопрос: как JVM гарантирует, что метод записи пишет f только после завершения конструктора ??? Что мешает записать f до завершения построения объекта ??? - person Mac; 11.08.2014

В полном примере

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) мы должны избегать совместного использования ссылки на неизменяемый объект до того, как его конструктор будет завершен.

Вы не должны разрешать ссылку на объект escape до его создания по ряду причин (неизменяемых или по другим причинам), например. объект может вызвать исключение после того, как вы сохранили объект.

В соответствии с приведенным примером (2) и выводом (3) JLS кажется, что мы можем безопасно делиться ссылкой на неизменяемый объект, т.е. когда все его поля являются окончательными.

Вы можете безопасно поделиться ссылкой на неизменяемый объект между потоками после того, как объект был построен.

Примечание: вы можете увидеть значение неизменяемого поля до того, как оно будет установлено в методе, вызываемом конструктором.

person Peter Lawrey    schedule 01.02.2013
comment
В вашей первой строке отсутствует не Питер? - person Gray; 01.02.2013
comment
До сих пор не понимаю. Я только что отредактировал свой вопрос. В моем 2) тезисе я имею в виду, что согласно (2) и (3) мы можем безопасно поделиться ссылкой до того, как конструктор будет завершен. Это непосредственно следует из примера (2). Не так ли? - person Andremoniy; 01.02.2013
comment
@ Андрей Я не понимаю, как вы пришли к такому выводу. В нем говорится, что final дает некоторые гарантии в конце строительства, а non-final - нет. Это не означает, что у вас есть какие-либо гарантии до окончания строительства. - person Peter Lawrey; 02.02.2013
comment
@PeterLawrey хорошо, рассмотрим класс, где все поля final. Будет ли безопасно делиться ссылкой на этот объект до того, как его конструктор будет завершен? По примеру будет. Потому что я не вижу разницы между моим воображаемым объектом и объектом из примера, где поле y будет final. - person Andremoniy; 02.02.2013
comment
@Andremoniy Вы, должно быть, читаете мне другой код. Когда я смотрю полный код, я этого совсем не вижу. - person Peter Lawrey; 02.02.2013
comment
Я только что отредактировал свой вопрос еще раз. Пожалуйста прочти. Будет ли это более понятным, что я имею в виду? :) - person Andremoniy; 02.02.2013
comment
@Andremoniy Я изменил пример, чтобы показать, как это может быть проблемой. - person Peter Lawrey; 02.02.2013
comment
Что ж, теперь я понял. Большое спасибо. - person Andremoniy; 02.02.2013
comment
Как видите, f не устанавливается до тех пор, пока конструктор не вернется .. Как мы можем быть в этом уверены? Что мешает записать f до завершения строительства? - person Mac; 11.08.2014

Конструктивный выход играет здесь важную роль; JLS сообщает: «Замораживание последнего поля f из 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].

person irreputable    schedule 01.02.2013
comment
Это имеет смысл. Спасибо за это объяснение, +1! - person Andremoniy; 02.02.2013

Утверждение 1) не говорит о том, что вы думаете. Во всяком случае, я бы перефразировал ваше утверждение:

1) Согласно утверждению (1) мы должны избегать совместного использования ссылки на неизменяемый объект до того, как его конструктор будет завершен.

читать

1) Согласно утверждению (1) мы должны избегать совместного использования ссылки на изменяемый объект до того, как его конструктор будет завершен.

Под изменяемым я подразумеваю объект, который имеет ЛЮБЫЕ неокончательные поля или окончательные ссылки на изменяемые объекты. (должен признать, я не на 100%, что вам нужно беспокоиться об окончательных ссылках на изменяемые объекты, но я думаю, что я прав ...)


Другими словами, следует различать:

  • поля final (неизменяемые части возможно неизменяемого объекта)
  • незавершенные поля, которые должны быть инициализированы, прежде чем кто-либо взаимодействует с этим объектом
  • неокончательные поля, которые не нужно инициализировать, прежде чем кто-либо взаимодействует с этим объектом

Второе - проблемное место.

Таким образом, вы можете делиться ссылками на неизменяемые объекты (все поля final), но вам нужно проявлять осторожность с объектами, у которых есть поля, отличные от final, которые ДОЛЖНЫ быть инициализированы до того, как объект может быть использован кем-либо.

Другими словами, для отредактированного примера JLS, который вы опубликовали, где оба поля равны final, int j = f.y; гарантированно будет окончательным. Но это означает, что вам НЕ нужно избегать записи ссылки на объект f, потому что он всегда будет в правильно инициализированном состоянии, прежде чем кто-либо сможет его увидеть. Вам не нужно беспокоиться об этом, это делает JVM.

person sharakan    schedule 01.02.2013
comment
Нет, в утверждении (1) ровно сказано о множестве final полей. - person Andremoniy; 01.02.2013
comment
@Andremoniy в вашем вопросе 1), вы хотели сказать изменчивый или неизменный? Потому что утверждение 1) сводится к тому, что вам нужно проявлять осторожность при совместном использовании изменяемых объектов. - person sharakan; 01.02.2013
comment
Если все поля в объекте final, мы можем рассматривать этот объект как immutable. И это тот случай, о котором говорит stmt (1). Не могли бы вы возразить? - person Andremoniy; 01.02.2013
comment
@Andremoniy нет, я на 100% согласен с этим утверждением. Я не согласен с той частью вашего вопроса, которая гласит: 1) Согласно утверждению (1) мы должны избегать совместного использования ссылки на неизменяемый объект .... Это должно читаться ... ссылка на изменяемый объект ... - person sharakan; 01.02.2013
comment
Итак, в заявлении (1) говорится об объекте с final полями. Не так ли? И говорят о том, что мы НЕ должны делиться этой ссылкой до завершения конструктора. Не так ли? - person Andremoniy; 01.02.2013
comment
Одно последнее поле, одно НЕ последнее. т.е. объект изменяемый. - person sharakan; 01.02.2013
comment
Пожалуйста, прочтите: Если это будет выполнено, то, когда объект будет виден другим потоком, этот поток всегда будет видеть правильно сконструированную версию последних полей этого объекта. Еще кое-что: Если это будет следовать ... blablabla ... последних полей этого объекта. Они говорят о final полях. Не так ли? - person Andremoniy; 02.02.2013
comment
в любом случае, можем ли мы рассмотреть класс, где все поля final? Будет ли это частным случаем для этого утверждения (1)? - person Andremoniy; 02.02.2013
comment
@Andremoniy Если есть класс, в котором все поля равны final, я бы считал этот класс неизменным (если эти поля относятся к неизменяемым классам и т. Д.), И было бы совершенно безопасно опубликовать этот объект до того, как конструктор вернется. - person sharakan; 02.02.2013
comment
позвольте нам продолжить обсуждение в чате - person Andremoniy; 02.02.2013