Как поток может видеть устаревшую ссылку на безопасно инициализированный объект - PullRequest
0 голосов
/ 27 января 2019

Я пытался выяснить, как можно наблюдать неизменные объекты, которые безопасно публикуются с устаревшей ссылкой.

public final class Helper {
private final int n;

  public Helper(int n) {
    this.n = n;
  }
} 

class Foo {
  private Helper helper;

  public Helper getHelper() {
    return helper;
  }

  public void setHelper(int num) {
    helper = new Helper(num);
  }
} 

До сих пор я мог понять, что Помощник является неизменным и может быть безопасно опубликован.Поток чтения либо читает нулевой, либо полностью инициализированный объект Helper, так как он не будет доступен до полной сборки.Решение состоит в том, чтобы поместить volatile в класс Foo, которого я не понимаю.

Ответы [ 3 ]

0 голосов
/ 27 января 2019

Переменная helper читается несколькими потоками одновременно. По крайней мере, вы должны сделать это volatile, иначе компилятор начнет кэшировать его в локальных регистрах потоков, и любые обновления переменной могут не отражаться в основной памяти. Используя volatile, когда поток начинает читать общую переменную, он очищает свой кэш и получает новое значение из глобальной памяти. Когда он заканчивает чтение, он сбрасывает содержимое своего кэша в основную память, чтобы другие потоки могли получить обновленное значение.

0 голосов
/ 27 января 2019

Тот факт, что вы публикуете ссылку на неизменный объект, здесь не имеет значения.

Если вы читаете значение ссылки из нескольких потоков, вам необходимо убедиться, что запись происходит до чтения, если вы заботитесь обо всех потоках, использующих наиболее актуальное значение.

Случается до - это точно определенный термин в спецификации языка, в частности, часть о модели памяти Java, которая позволяет потокам оптимизировать, например, не всегда обновляя данные в основной памяти (что является медленно), вместо этого удерживая их в локальном кэше (что гораздо быстрее, но может привести к тому, что потоки будут содержать разные значения для «одной и той же» переменной). Happens-before - это отношение, которое помогает вам понять, как несколько потоков взаимодействуют при использовании этих оптимизаций.

Если вы на самом деле не создаете отношения «до того, как это произойдет», нет гарантии, что вы увидите самую последнюю ценность. В показанном вами коде нет такой взаимосвязи между записью и чтением helper, поэтому ваши потоки не гарантированно увидят «новые» значения helper. Они могут , но, скорее всего, не будут.

Самый простой способ убедиться, что запись происходит перед чтением, - это сделать helper переменную-член final: запись в значения полей final гарантированно произойдет до конца конструктора, поэтому все потоки всегда видят правильное значение поля (при условии, что this не пропущено в конструкторе).

Сделать это final здесь не вариант, видимо, потому что у вас есть сеттер. Поэтому вы должны использовать какой-то другой механизм.

Если взять код по номиналу, простейшим вариантом будет использование (окончательного) AtomicInteger вместо Helper класса: запись в AtomicInteger гарантированно произойдет перед последующим чтением. Но я думаю, что ваш настоящий класс помощника, вероятно, более сложный.

Итак, вы должны сами создать такие отношения до того, как это произойдет. Три механизма для этого:

  • Использование AtomicReference<Helper>: имеет семантику, аналогичную AtomicInteger, но позволяет вам хранить значение с типом ссылки. (Спасибо за указание на это, @Thilo).
  • Создание поля volatile: это гарантирует видимость самого последнего записанного значения, поскольку оно приводит к сбросу записей в основную память (в отличие от чтения из кэша потока) и чтению для чтения из основной памяти. Это эффективно останавливает JVM, выполняющую эту конкретную оптимизацию.
  • Доступ к полю в синхронизированном блоке. Проще всего было бы синхронизировать методы getter и setter. Важно отметить, что вы не должны синхронизироваться на helper, так как это поле изменяется.
0 голосов
/ 27 января 2019

Цитировать из Volatile против статического в Java

Это означает, что если два потока одновременно обновляют переменную одного и того же объекта, а переменная не объявлена ​​как volatile, томожет быть случай, когда один из потоков имеет в кеше старое значение.

Учитывая ваш код, может произойти следующее:

  • Поток 1 вызывает getHelper () иполучает ноль
  • Поток 2 вызывает getHelper () и получает ноль
  • Поток 1 вызывает setHelper (42)
  • Поток 2 вызывает setHelper (24)

И в этом случае начинаются проблемы с тем, какой объект Helper будет использоваться в каком потоке.Ключевое слово volatile как минимум решит проблему кеширования.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...