Необходимость записи в энергозависимый массив в синхронизированном блоке - PullRequest
8 голосов
/ 12 января 2012

Вопрос относительно JMM и семантики относительно изменчивых полей, которые записываются в синхронизированный блок, но читаются несинхронизированными.

В первоначальной версии приведенного ниже кода я не синхронизировал доступ, поскольку он был ненужен для более ранних требований (и злоупотребление самоопределением this.cache = this.cache обеспечило семантику изменчивой записи). Определенные требования изменились, что потребовало синхронизации, чтобы гарантировать, что повторяющиеся обновления не будут отправлены. Вопрос, который у меня возникает, состоит в том, исключает ли блок синхронизации требование самостоятельного назначения изменяемого поля?

  // Cache of byte[] data by row and column.
  private volatile byte[][][] cache;

  public byte[] getData(int row, int col)
  {
    return cache[row][col];
  }

  public void updateData(int row, int col, byte[] data)
  {
    synchronized(cache)
    {
      if (!Arrays.equals(data,cache[row][col]))
      {
        cache[row][col] = data;

        // Volatile write.
        // The below line is intentional to ensure a volatile write is
        // made to the array, since access via getData is unsynchronized.
        this.cache = this.cache;

        // Notification code removed
        // (mentioning it since it is the reason for synchronizing).
      }
    }
  }

Без синхронизации я считаю, что энергозависимая запись самопредставления технически необходима (хотя IDE помечает ее как не имеющую эффекта). С синхронизированным блоком я думаю, что это все еще необходимо (поскольку чтение не синхронизировано), но я просто хочу подтвердить, так как это выглядит нелепо в коде, если это на самом деле не требуется. Я не уверен, есть ли какие-либо гарантии, о которых я не знаю между концом синхронизированного блока и изменчивым чтением.

Ответы [ 3 ]

4 голосов
/ 12 января 2012

Да, вам все еще нужна энергозависимая запись, согласно модели памяти Java. Порядок синхронизации от разблокировки cache отсутствует для последующего изменчивого чтения cache: разблокировка -> volatileRead не гарантирует видимость. Вам нужно либо разблокировать -> заблокировать , либо volatileWrite -> volatileRead .

Однако у реальных JVM гораздо более сильные гарантии памяти. Обычно unlock и volatileWrite имеют одинаковый эффект памяти (даже если они имеют разные переменные); такой же как lock и volatileRead .

Итак, у нас здесь дилемма. Типичная рекомендация - строго следовать спецификации. Если у вас нет очень широких знаний по этому вопросу. Например, код JDK может использовать некоторые уловки, которые не являются теоретически правильными; но код ориентирован на конкретную JVM, а автор является экспертом.

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

Ваш код правильный и эффективный; однако это вне типичных образцов; Я бы немного подправил:

  private final    byte[][][] cacheF = new ...;  // dimensions fixed?
  private volatile byte[][][] cacheV = cacheF;

  public byte[] getData(int row, int col)
  {
    return cacheV[row][col];
  }

  public void updateData(int row, int col, byte[] data)
  {
    synchronized(cacheF)
    {
      if (!Arrays.equals(data,cacheF[row][col]))
      {
        cacheF[row][col] = data;

        cacheV = cacheF; 
      }
    }
  }
3 голосов
/ 12 января 2012

Запись индекса в энергозависимый массив фактически не имеет эффектов памяти. То есть, если вы уже создали экземпляр массива, то если поле, объявленное как volatile, не даст вам семантику памяти, которую вы ищете при назначении элементов в массиве.

Другими словами

private volatile byte[][]cache = ...;
cache[row][col] = data;

Имеет ту же семантику памяти, что и

private final byte[][]cache = ...;
cache[row][col] = data;

Из-за этого вы должны синхронизировать все операции чтения и записи в массив. И когда я говорю «та же семантика памяти», я имею в виду, что нет никакой гарантии, что потоки будут читать самое актуальное значение cache[row][col]

3 голосов
/ 12 января 2012

Самостоятельное назначение гарантирует, что другой поток будет читать заданную ссылку на массив, а не другую ссылку на массив. Но у вас может быть один поток, модифицирующий массив, в то время как другой поток читает его.

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

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