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