Отложенная инициализация в неизменяемом классе - нужен ли volatile? И КАК он здесь нужен?
Вроде старый и общеизвестный вопрос, но я вдруг обнаружил, что не вполне понимаю, как правильно.
Есть неизменяемый класс, допустим, SimplePattern. (Буду брать примеры из моего реального пакета; речь идет о "простой" реализации точечного множества). В нем есть метод
public Pattern leftBoundary(int coordIndex)
(возвращающий "левую границу" фигуры вдоль заданной координатной оси). Метод потенциально "долгоиграющий" - для паттернов из 100 точек работает мгновенно, но для паттернов из миллиона точек может работать очень долго. Соответственно, применяется отложенная инициализация. А именно, объявляю поле
private final Pattern[] leftBoundary;
и пишу так:
Для большей кошерности "отложенно инициализируемый" массив объявлен final: его создает конструктор в виде массива null-ов (по числу пространственных измерений).
А теперь классический вопрос на синхронизацию: что будет при использовании из нескольких потоков? Класс неизменяем, стало быть, предполагается, что абсолютно потоково-безопасен.
Блох по этому поводу непринужденно пишет (про хэш класса String): "никакой синхронизации здесь не требуется, поскольку никаких проблем с повторным вычислением хэша не возникает". Однако в своем тексте общей идиомы, без каких-либо пояснений, помечает "отложенное" объектное поле как volatile.
Так что, и правда нужен volatile? Ведь иначе на многопроцессорной конфигурации мой код в состоянии "увидеть" leftBoundary[coordIndex], ссылающийся на частично заполненную (например, частично скопированную между процессорами) структуру.
Самое смешное, что модификатор final при этом компилятор заставляет убрать. Теперь я написал так:
private volatile transient Pattern[] leftBoundary;
Правильно ли это? Достаточно ли объявить volatile весь массив, чтобы его элементы тоже приобрели такое свойство? Насколько я понял объяснения новой модели памяти ("происходит-прежде" и все такое), вроде бы так, но полной уверенности у меня нет. Особенно смущает необходимость убирать final - хотя ссылка на массив, разумеется, никогда не меняется.
Прокомментируйте, пожалуйста.
Есть неизменяемый класс, допустим, SimplePattern. (Буду брать примеры из моего реального пакета; речь идет о "простой" реализации точечного множества). В нем есть метод
public Pattern leftBoundary(int coordIndex)
(возвращающий "левую границу" фигуры вдоль заданной координатной оси). Метод потенциально "долгоиграющий" - для паттернов из 100 точек работает мгновенно, но для паттернов из миллиона точек может работать очень долго. Соответственно, применяется отложенная инициализация. А именно, объявляю поле
private final Pattern[] leftBoundary;
и пишу так:
public Pattern leftBoundary(int coordIndex) {
if (leftBoundary[coordIndex] == null) {
... долгое и сложное вычисление нужного паттерна result
leftBoundary[coordIndex] = result;
}
return leftBoundary[coordIndex];
Для большей кошерности "отложенно инициализируемый" массив объявлен final: его создает конструктор в виде массива null-ов (по числу пространственных измерений).
А теперь классический вопрос на синхронизацию: что будет при использовании из нескольких потоков? Класс неизменяем, стало быть, предполагается, что абсолютно потоково-безопасен.
Блох по этому поводу непринужденно пишет (про хэш класса String): "никакой синхронизации здесь не требуется, поскольку никаких проблем с повторным вычислением хэша не возникает". Однако в своем тексте общей идиомы, без каких-либо пояснений, помечает "отложенное" объектное поле как volatile.
Так что, и правда нужен volatile? Ведь иначе на многопроцессорной конфигурации мой код в состоянии "увидеть" leftBoundary[coordIndex], ссылающийся на частично заполненную (например, частично скопированную между процессорами) структуру.
Самое смешное, что модификатор final при этом компилятор заставляет убрать. Теперь я написал так:
private volatile transient Pattern[] leftBoundary;
Правильно ли это? Достаточно ли объявить volatile весь массив, чтобы его элементы тоже приобрели такое свойство? Насколько я понял объяснения новой модели памяти ("происходит-прежде" и все такое), вроде бы так, но полной уверенности у меня нет. Особенно смущает необходимость убирать final - хотя ссылка на массив, разумеется, никогда не меняется.
Прокомментируйте, пожалуйста.
