Самый дешевый способ установления происходит до того,

Многие вопросы/ответы указали, что если объект класса имеет поле final, и никакая ссылка на него не подвергается ни одному другому потоку во время построения, тогда все потоки гарантированно будут видеть значение, записанное в поле, когда конструктор завершается. Они также указали, что сохранение в поле final ссылки на изменяемый объект, к которому никогда не обращались внешние потоки, гарантирует, что все мутации, которые были сделаны объекту перед хранилищем, будут видны во всех потоках, объект через поле. К сожалению, ни одна из гарантий не распространяется на записи полей не final.

Однако вопрос, который я не вижу, заключается в следующем: если семантика класса такова, что поле не может быть final, но мы хотим обеспечить "публикацию" поля и объекта, идентифицированного таким образом, каков наиболее эффективный способ сделать это? В качестве примера рассмотрим

class ShareableDataHolder<T>
{
  Object data; // Always identifies either a T or a SharedDataHolder<T>
}
private class SharedDataHolder<T> extends ShareableDataHolder<T>
{
  Object data; // Always identifies either a T or a lower-numbered SharedDataHolder<T>
  final long seq; // Immutable; necessarily unique
}

Предполагалось, что data будет сначала идентифицировать объект данных напрямую, но он может быть законным в любое время изменен для идентификации SharedDataHolder<T>, который прямо или косвенно инкапсулирует эквивалентный объект данных. Предположим, что весь код написан правильно (хотя и не обязательно оптимально-эффективно), если любое чтение data может произвольно возвращать любое значение, которое когда-либо было написано на data, но может выйти из строя, если оно читает null.

Объявление volatile Object data будет семантически правильным, но, скорее всего, наложит дополнительные затраты на каждый последующий доступ к полю. Ввод фиктивного замка после первоначальной установки поля будет работать, но будет бесполезно медленным. Наличие фиктивного поля final, которое объект устанавливает для идентификации, похоже, что он должен работать; хотя технически я думаю, что это может потребовать, чтобы все обращения к другому полю были сделаны через другое поле, я не вижу реалистичного сценария, где это имеет значение. В любом случае наличие фиктивного поля, целью которого является только обеспечить соответствующую синхронизацию через его существование, представляется расточительным.

Есть ли какой-либо чистый способ сообщить компилятору, что конкретная запись в data внутри конструктора должна иметь отношение "до-до" относительно любых чтений этого поля, которые возникают после возвращения конструктора (как это было бы в случае если поле было final), без необходимости оплачивать расходы, связанные с volatile, блокировки и т.д.? В качестве альтернативы, если поток должен был читать data и найти его null, может ли он каким-то образом повторить чтение таким образом, чтобы установить "произойдет после" в отношении записи data [признавая, что такой запрос может быть медленным, но не обязательно, чтобы это происходило очень часто]?

PS - Если происходит - до того, как отношения являются нетранзитивными, произойдет ли правильное действие - до того, как отношения будут существовать в следующем сценарии?

  • Thread 1 записывает в не конечное поле dat в некоторый объект Fred и сохраняет ссылку на него в конечное поле George.
  • Резьба 2 копирует ссылку из George в поле без полей Larry.
  • Тема 3 читает Larry.dat.

Из того, что я могу сказать, между записью поля Fred dat и чтением George существует связь между случаем и прошлым. Было бы существовать до отношения между записью Fred dat и чтением Larry, которая возвращает ссылку на Fred, которая была скопирована из ссылки final на Fred? Если нет, существует ли какой-либо "безопасный" способ скопировать ссылку, содержащуюся в поле final, в поле не окончательного значения, которое будет доступно через другие потоки?

PPS. Если объект и его составляющие никогда не обращаются за пределами своей создаваемой нити до тех пор, пока главный конструктор не закончит, а последний шаг главного конструктора будет состоять из хранилищ в главном объекте a final ссылка на себя, есть ли любая "правдоподобная" реализация/сценарий, когда другой поток мог видеть частично построенный объект, независимо от того, действительно ли что-то использует эту ссылку final?

Ответ 1

Короткий ответ

Нет.

Более длинный ответ

JLS 17.4.5 перечисляет все * способов установления связи между событиями, отличными от специального случая семантики поля final:

  • Разблокировка на мониторе происходит до каждой последующей блокировки на этом мониторе.
  • Записывается в поле volatile (§8.3.1.4) - перед каждым последующим чтением этого поля.
  • Запускается вызов start() в потоке - перед любыми действиями в запущенном потоке.
  • Все действия в потоке происходят до того, как какой-либо другой поток успешно возвращается из соединения() в этом потоке.
  • Инициализация по умолчанию любого объекта происходит - перед любыми другими действиями (отличными от записи по умолчанию) программы.

(Оригинал перечисляет их как точки маркера, я меняю их на цифры для удобства здесь.)

Теперь вы исключили блокировки (# 1) и изменчивые поля (# 2). Правила № 3 и № 4 относятся к жизненному циклу потока, о котором вы не упоминаете в своем вопросе, и звучит не так, как если бы это применимо. Правило №5 не дает вам никаких значений не null, поэтому оно также не применяется.

Итак, из пяти возможных методов для определения бывает, прежде чем, кроме семантики поля final, три не применяются и два, которые вы явно исключили.


* Правила, перечисленные в 17.4.5, на самом деле являются следствием правил порядка синхронизации, определенных в 17.4.4, но они относятся непосредственно к тем, которые упомянуты в 17.4.5. Я упоминаю, что, поскольку список 17.4.5 можно интерпретировать как иллюстративный и, следовательно, не исчерпывающий, но список 17.4.4 не является иллюстративным и исчерпывающим, и вы можете сделать тот же анализ из этого напрямую, если вы не хотите полагайтесь на промежуточный анализ, который предоставляется в разделе 17.4.5.

Ответ 2

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

class Publisher<T> {
  private final T value;
  private Publisher(T value) { this.value = value; }
  public static <S> S publish(S value) { return new Publisher<S>(value).value; }
}

Если вы сейчас работаете с экземпляром ShareableDataHolder<T>, вы можете опубликовать его:

ShareableDataHolder<T> holder = new ShareableDataHolder<T>();
// set field values
holder = Publisher.publish(holder);
// Passing holder to other threads is now safe

Этот подход проверен и протестирован и оказался наиболее эффективной альтернативой для текущих виртуальных машин. Накладные расходы минимальны, так как анализ эвакуации обычно удаляет выделение очень короткого экземпляра Publisher.