Эффекты видимости синхронизации в Java - PullRequest
0 голосов
/ 14 октября 2018

В этой статье говорится:

В этом несовместимом примере кода класс Helper становится неизменным, объявляя его поля final.JMM гарантирует, что неизменяемые объекты полностью построены, прежде чем они станут видимыми для любого другого потока.Синхронизация блоков в методе getHelper () гарантирует, что все потоки, которые могут видеть ненулевое значение поля помощника, также увидят полностью инициализированный объект помощника.

public final class Helper {
  private final int n;

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

  // Other fields and methods, all fields are final
}

final class Foo {
  private Helper helper = null;

  public Helper getHelper() {
    if (helper == null) {            // First read of helper
      synchronized (this) {
        if (helper == null) {        // Second read of helper
          helper = new Helper(42);
        }
      }
    }

    return helper;                   // Third read of helper
  }
}

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

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

Может ли кто-нибудь разработать более умело?

Ответы [ 3 ]

0 голосов
/ 14 октября 2018

Здесь все объяснено https://shipilev.net/blog/2014/safe-public-construction/#_singletons_and_singleton_factories, проблема проста.

... Обратите внимание, что мы делаем несколько чтений экземпляра в этом коде, и, по крайней мере, «чтение 1» и «чтение 3» являются чтениями без какой-либо синхронизации ... По спецификации, какупомянутое в правилах согласования «случается до», действие чтения может наблюдать неупорядоченную запись через гонку.Это решается для каждого действия чтения, независимо от того, какие другие действия уже прочитали то же место.В нашем примере это означает, что, хотя «read 1» может читать ненулевой экземпляр, код затем переходит к его возвращению, затем выполняет еще одно случайное чтение и может читать нулевой экземпляр, который будет возвращен!

0 голосов
/ 14 октября 2018

Нет, переходных отношений нет.

Идея JMM состоит в том, чтобы определить правила, которые JVM должна соблюдать.При условии, что JVM следует этим правилам, им разрешается переупорядочивать и выполнять код по своему усмотрению.

В вашем примере 2-е чтение и 3-е чтение не связаны - нет барьера памяти при использовании synchronized или volatile например.Таким образом, JVM разрешено выполнять его следующим образом:

 public Helper getHelper() {
    final Helper toReturn = helper;  // "3rd" read, reading null
    if (helper == null) {            // First read of helper
      synchronized (this) {
        if (helper == null) {        // Second read of helper
          helper = new Helper(42);
        }
      }
    }

    return toReturn; // Returning null
  }

Ваш вызов тогда возвратит нулевое значение.Тем не менее, единственное значение было бы создано.Тем не менее, последующие вызовы могут по-прежнему принимать нулевое значение.

Как и предполагалось, использование volatile создаст новый барьер памяти.Другое распространенное решение - захватить прочитанное значение и вернуть его.

 public Helper getHelper() {
    Helper singleton = helper;
    if (singleton == null) {
      synchronized (this) {
        singleton = helper;
        if (singleton == null) {
          singleton = new Helper(42);
          helper = singleton;
        }
      }
    }

    return singleton;
  }

Поскольку вы полагаетесь на локальную переменную, переупорядочивать нечего.Все происходит в одной теме.

0 голосов
/ 14 октября 2018

Нет, между этими чтениями нет переходных отношений.synchornized гарантирует только видимость изменений, которые были сделаны в синхронизированных блоках одной и той же блокировки.В этом случае во всех операциях чтения не используются синхронизированные блоки в одной и той же блокировке, поэтому это является ошибкой и видимость не гарантируется.

Поскольку блокировка не выполняется после инициализации поля, важно, чтобы поле былообъявлено volatile.Это обеспечит видимость.

private volatile Helper helper = null;
...