Java: Зафиксировать конечное поле на объекте, доступном из конечных полей

Реплика DZone под названием " Core Java Concurrency" указывает:

После установки значения конечного поля не может быть изменено. Пометить поле ссылки объекта как final не препятствовать замене объектов, ссылающихся на это поле. Для Например, конечное поле ArrayList не может быть изменено на другое ArrayList, но объекты могут быть добавлены или удалены в экземпляре списка.

и

Завершение окончательного поля включает не только конечные поля в объекте, но и все объектов, доступных из этих конечных полей.

Я не совсем понимаю о втором утверждении. Означает ли это, что если у меня есть последнее поле в классе A типа Class B, которое, в свою очередь, имеет окончательное поле типа Integer, то окончательное замораживание поля для экземпляра класса A завершается только после окончательного замораживания поля для b.c уже произошло?

public class A{

  public final B b = new B();

}

public class B{ 

  public final Integer c = 10;

}

Ответ 1

Означает ли это, что если у меня есть окончательный поле в классе A типа Class B, которые, в свою очередь, имеют последнее поле введите Integer, затем окончательное замораживание поля для экземпляра класса A завершается только после окончательного замораживания поля для b.c уже произошло?

Я думаю, что я бы сказал, что окончательное замораживание поля в этом случае означает, что при создании экземпляра A и его безопасного опубликования другие объекты никогда не будут видеть неинициализированное значение для b или c.

Я бы также сказал, что когда вы создаете экземпляр B внутри A, другой код инициализации внутри A никогда не увидит неинициализированное значение для c.

В одном случае, когда я столкнулся с реальными вопросами об окончательном замораживании полей, например, класс, содержащий (изменяемый) HashMap, предназначенный только для чтения, инициализированный во время построения:

public class DaysOfWeek {
    private final Map daysOfWeek = new HashMap();
    public DaysOfWeek() { 
      // prepopulate my map
      daysOfWeek.put(0, "Sunday");
      daysOfWeek.put(1, "Monday");
      // etc
    }

    public String getDayName(int dayOfWeek) {
      return daysOfWeek(dayOfWeek);
    }
}

Здесь возникает вопрос: предполагая, что этот объект безопасно опубликован, и учитывая, что здесь нет синхронизации, безопасно ли для других потоков вызывать getDayName()? Ответ - да, потому что окончательное замораживание поля гарантирует, что HashMap и все, что доступно от него (здесь это просто строки, но могут быть произвольно сложными объектами) замораживается в конце построения. [Если вы хотите на самом деле изменить эту карту после построения, вам понадобится явная синхронизация вокруг чтения и записи.] Здесь длинный блог, исследующий темы и проверить комментарии для некоторых интересных ответов таких людей, как Брайан Гетц.

btw Я являюсь автором refcard

Ответ 2

Java Concurrency на практике упоминает это в разделе 16.3:

Безопасность инициализации гарантирует, что для правильно построенных объектов, все потоки будут видеть правильные значения конечных полей, которые были установлены конструктором, независимо от того, как объект опубликован. Кроме того, любые переменные, которые могут быть достигнуты через конечное поле надлежащего построенный объект (например, элементы конечного массива или содержимое HashMap, на которое ссылается конечное поле) также гарантировано видимый для других потоков. Для объектов с конечными полями, инициализация безопасность запрещает переупорядочивать любую часть конструкции с начальной нагрузкой ссылки на этот объект. Все пишет в окончательные поля, сделанные конструктора, а также любого переменные, достигаемые через эти полей, становятся "замороженными", когда конструктор завершается, и любая нить который получает ссылку на это объекту гарантировано увидеть значение что, по крайней мере, замороженное значение. Записывает, что инициализирует переменные достижимы через окончательный поля не переупорядочиваются с операций после послезаводская заморозка.

Ответ 3

Right. Это следует из JMM

Ищите абзац:

Объект считается полностью инициализированным, когда его конструктор заканчивается. Поток, который может видеть только ссылку на объект после того, как этот объект был полностью инициализирован, гарантированно увидит правильно инициализированные значения для окончательных полей этого объекта.

Поскольку конструктор не будет завершен до тех пор, пока не будет инициализирован класс B, гарантирующий замораживание B.c

Ответ 4

Гарантия сильнее, чем кажется. Окончательная семантика поля применяется даже к изменяемым объектам, которые назначаются конечным полям (с обычными ограничениями). SO расширяет ваш пример, чтобы сделать A.b private и B изменчивым (но не измененным извне).

public class A {
    private final B b = new B();
    public Integer get() { return b.c; }
}

public class B {
    public Integer c = 10;
}

В этом случае A.get никогда не вернет null даже в небезопасной публикации. Конечно, этот пример полностью абстрактен и, следовательно, бессмыслен. Обычно это важно для массивов (например, в String) и коллекций.

Ответ 5

На самом деле не имеет смысла говорить о том, что становится окончательным до того, что еще. В свою программу, как только ваш объект будет создан (фактически с момента, когда поле назначается один раз), ссылка больше не может измениться. Поскольку экземпляр B создается до экземпляра A, вы можете сказать, что c становится окончательным до b, но это не имеет особого значения.

Если порядок важен, это когда у вас несколько конечных полей в одном классе. Если вы хотите использовать значение одного конечного поля при назначении другого, вы должны получить доступ только к уже инициализированным полям.

Честно говоря, это предложение "окончательное исключение поля" для меня не имеет большого значения.