потокобезопасен без летучих - PullRequest
4 голосов
/ 02 февраля 2012

Может кто-нибудь объяснить, почему этот пример безопасен для потоков без volatile?

http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

Фактически, предполагая, что функция computeHashCode всегда возвращала один и тот же результат и не имела побочных эффектов (то есть идемпотентно), вы могли бы даже избавиться от всей синхронизации.

// Lazy initialization 32-bit primitives
// Thread-safe if computeHashCode is idempotent
class Foo { 
  private int cachedHashCode = 0;
  public int hashCode() {
    int h = cachedHashCode;
    if (h == 0) {
      h = computeHashCode();
      cachedHashCode = h;
      }
    return h;
    }
  // other functions and members...
  }

БОЛЬШЕ: Я понял, нам все равно, вычисляется ли значение дважды (поэтому оно не является действительно поточно-ориентированным). Я также хотел бы знать, гарантированно ли новые потоки, созданные после вычисления хеш-кода, увидят новый хеш-код?

Ответы [ 4 ]

7 голосов
/ 02 февраля 2012

Это хождение по тонкому льду, но вот объяснение. Проблема видимости означает, что некоторые потоки могут видеть старую версию, а некоторые - новую. В нашем случае некоторые потоки видят 0, а другие - cachedHashCode.

Потоки, которые вызывают hashCode() и видят cachedHashCode, просто вернут его (if (h == 0) условие не выполнено) и все работает.

Но потоки, которые видят 0 (несмотря на то, что cachedHashCode, возможно, уже были вычислены), просто снова пересчитают его.

Другими словами, в худшем случае каждый поток войдет в ветвь, впервые увидев 0 (например, если бы это было ThreadLocal).

Так как computeHashCode() является идемпотентом (очень важно), то как вызов его несколько раз (разными потоками), так и переназначение его одной и той же переменной не должно иметь побочных эффектов.

6 голосов
/ 02 февраля 2012

Важной информацией здесь является

Функция computeHashCode всегда возвращает один и тот же результат

Если это так, то известно, что computeHashCode эффективно неизменяем и, посколькуэто всегда будет то же значение, которое никогда не будет иметь проблемы с параллелизмом.

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

4 голосов
/ 02 февраля 2012

Это так называемая идиома.Он используется, когда вычисление значения идемпотентно (каждый раз возвращает одно и то же значение; следствие: тип должен быть неизменным) и дешевое (если оно случайно пересчитано более одного раза, это нормально).Это всегда принимает некоторую форму в соответствии с

class Foo {
  private Value cacheField; // field holding the cached value; not volatile!

  public Value getValue() {
    Value value = cacheField; // very important!
    if (value == 0 or null or whatever) {
      value = computeValue();
      cacheField = value;
    }
    return value;
  }
}

или чем-то более или менее эквивалентным.Если ваша реализация не идемпотентна или не дешева, то вам следует использовать другую идиому;см. Эффективный элемент Java 71 для деталей.Но дело в том, что потоки выполняют не более одного чтения до cacheField, и если они видят cacheField в состоянии, в котором значение не было вычислено, они пересчитывают это значение.Java, вот как, например, String.hashCode() реализовано.

0 голосов
/ 02 февраля 2012

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

В противном случае я не согласен с тем, что это будет потокобезопасным.

  • Поток 1 вводит Hashcode (), проходит половину пути и делает паузу перед возвратом ответа 5 (например).
  • Поток 2 вводит computeHashCode () и делает паузына полпути через него
  • Поток 3 изменяет объект таким образом, что влияет на хэш-код.
  • Поток 1 перезапускается, устанавливает хэш-код равным 5 и помещает объект в хэш-карту в качестве ключа.
  • Поток 2 перезапускается, устанавливает хэш-код на 78392.

Поток 1 может или не сможет найти ключ в хэш-карте позже, в зависимости от того, как jvm выбрал обработку этогоcachedHashCode.У jvm есть возможность хранить отдельные копии энергонезависимого поля, если ему это нравится.Volatile только гарантирует, что jvm этого не сделает, и что все потоки всегда будут видеть одно и то же значение.

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