Тот факт, что вы публикуете ссылку на неизменный объект, здесь не имеет значения.
Если вы читаете значение ссылки из нескольких потоков, вам необходимо убедиться, что запись происходит до чтения, если вы заботитесь обо всех потоках, использующих наиболее актуальное значение.
Случается до - это точно определенный термин в спецификации языка, в частности, часть о модели памяти Java, которая позволяет потокам оптимизировать, например, не всегда обновляя данные в основной памяти (что является медленно), вместо этого удерживая их в локальном кэше (что гораздо быстрее, но может привести к тому, что потоки будут содержать разные значения для «одной и той же» переменной). Happens-before - это отношение, которое помогает вам понять, как несколько потоков взаимодействуют при использовании этих оптимизаций.
Если вы на самом деле не создаете отношения «до того, как это произойдет», нет гарантии, что вы увидите самую последнюю ценность. В показанном вами коде нет такой взаимосвязи между записью и чтением helper
, поэтому ваши потоки не гарантированно увидят «новые» значения helper
. Они могут , но, скорее всего, не будут.
Самый простой способ убедиться, что запись происходит перед чтением, - это сделать helper
переменную-член final
: запись в значения полей final
гарантированно произойдет до конца конструктора, поэтому все потоки всегда видят правильное значение поля (при условии, что this
не пропущено в конструкторе).
Сделать это final
здесь не вариант, видимо, потому что у вас есть сеттер. Поэтому вы должны использовать какой-то другой механизм.
Если взять код по номиналу, простейшим вариантом будет использование (окончательного) AtomicInteger
вместо Helper
класса: запись в AtomicInteger
гарантированно произойдет перед последующим чтением. Но я думаю, что ваш настоящий класс помощника, вероятно, более сложный.
Итак, вы должны сами создать такие отношения до того, как это произойдет. Три механизма для этого:
- Использование
AtomicReference<Helper>
: имеет семантику, аналогичную AtomicInteger
, но позволяет вам хранить значение с типом ссылки. (Спасибо за указание на это, @Thilo).
- Создание поля
volatile
: это гарантирует видимость самого последнего записанного значения, поскольку оно приводит к сбросу записей в основную память (в отличие от чтения из кэша потока) и чтению для чтения из основной памяти. Это эффективно останавливает JVM, выполняющую эту конкретную оптимизацию.
- Доступ к полю в синхронизированном блоке. Проще всего было бы синхронизировать методы getter и setter. Важно отметить, что вы не должны синхронизироваться на
helper
, так как это поле изменяется.