Недавно я начал изучать C# и сразу перешел к модели памяти. C# и Java имеют схожие (хотя, возможно, и не идентичные) гарантии безопасности потоков в отношении чтения и записи volatile
полей. Но в отличие от записи в поля final
в Java, запись в поля readonly
в C# не дает какой-либо конкретной гарантии безопасности потока. Размышление о том, как работает безопасность потоков в C#, заставило меня усомниться в том, есть ли какое-то реальное преимущество в том, что final
полей ведут себя так, как в Java.
Я узнал о предполагаемой важности final
три года назад. Я задал этот вопрос и получил подробный ответ, который я принял. Но теперь я думаю, что это неправильно или, по крайней мере, неуместно. Я по-прежнему считаю, что поля должны быть final
, когда это возможно, но не по общепринятым причинам.
Значение поля final
гарантированно будет видно любому другому потоку после возврата из конструктора. Но ссылка на сам объект должна быть опубликована потокобезопасным способом. Если ссылка опубликована безопасно, гарантия видимости final
становится избыточной.
Я рассматривал возможность того, что это как-то связано с public static
полями. Но логически загрузчик классов должен синхронизировать инициализацию класса. Синхронизация делает безопасность потоков final
избыточной.
Итак, я выдвигаю еретическую идею о том, что единственная реальная ценность final
состоит в том, чтобы сделать неизменяемость самодокументируемой и самодостаточной. На практике закрытые поля, не являющиеся конечными, (и, в частности, элементы массива) полностью потокобезопасны, если они не изменяются после возврата из конструктора.
Я ошибся?
Изменить: перефразируя раздел 3.5 Java Concurrency in Practice,
Две вещи могут пойти не так с неправильно опубликованными объектами. Другие потоки могут увидеть устаревшее значение для ссылки и, таким образом, увидеть нулевую ссылку или другое более старое значение, даже если значение было установлено. Но что еще хуже, другие потоки могут видеть актуальное значение для ссылки, но устаревшие значения для состояния объекта.
Я понимаю, как final
полей решают вторую проблему, но не первую проблему. На данный момент ответ с наибольшим количеством голосов утверждает, что первая проблема не является проблемой.
Изменить 2. Этот вопрос возникает из-за путаницы в терминологии.
Как и автор вопроса аналогичного вопроса, я всегда понимал термин "безопасная публикация" в том смысле, что внутреннее состояние объекта и ссылка на сам объект гарантированно видны другим потокам. В пользу этого определения Effective Java цитирует Goetz06, 3.5.3 в определении «безопасной публикации» как (курсив добавлен)
Перенос такой ссылки на объект из одного потока в другой
Также в пользу этого определения обратите внимание на то, что парафразированный выше раздел Java Concurrency in Practice относится к потенциально устаревшим ссылкам как к «неправильно опубликованным».
Как бы вы это ни называли, я не думал, что небезопасная публикация ссылки на неизменяемый объект может быть полезной. Но согласно этому ответу это возможно. (Приведенный здесь пример представляет собой примитивное значение, но тот же принцип можно применить и к ссылочным значениям.)