线程安全无波动

谁能解释为什么这个例子是没有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... } 

更多:我明白了,我们不关心值是否计算两次(因此它不是真正的线程安全)。 我还想知道在计算哈希码之后创建的新线程是否可以保证看到新的哈希码?

这是在冰上行走,但这是解释。 可见性问题意味着某些线程可能会看到旧版本和一些新版本。 在我们的例子中,一些线程看到0而其他线程看到cachedHashCode

调用hashCode()并查看cachedHashCode将只返回它( if (h == 0)条件不满足)并且一切正常。

但是看到0线程(尽管可能已经计算了cachedHashCode )将再次重新计算它。

换句话说,在最坏的情况下,每个线程将首次进入看到0的分支(就像它是ThreadLocal )。

由于computeHashCode()是幂等的(非常重要),因此多次调用它(通过不同的线程)并将其重新分配给同一个变量不应该有任何副作用。

这里重要的信息是

computeHashCode函数始终返回相同的结果

如果这是真的,那么已知computeHashCode实际上是不可变的,因为它总是相同的值,你永远不会有并发问题。

至于使cachedHashCode易变。 就线程安全而言,它不会有所作为,因为你总是分配并返回一个线程局部变量h ,它将是一个非零的computedHashCode。

这被称为活泼的单一检查成语。 它在计算一个值是幂等的时候使用(每次都返回相同的值;推论:类型必须是不可变的)并且便宜(如果它被意外重新计算不止一次那就没关系)。 这总是采取某种forms

 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; } } 

或者或多或少等同的东西。 如果你的实现不是幂等的,或者不便宜,那么你应该使用另一个成语; 有关详细信息,请参阅Effective Java第71项。 但重点是,线程最多cacheField读取一次到cacheField ,如果他们看到cacheField处于尚未计算值的状态, cacheField重新计算该值。

正如Effective Java中所提到的,这就是String.hashCode()的实现方式。

如果Foo对于有助于哈希码的字段是不可变的,那只会是真的。 (必须满足“假设computeHashCode函数总是返回相同的结果”)

否则我不同意它是线程安全的。

  • 线程1进入Hashcode(),中途通过它,并在返回5的答案之前暂停(例如)。
  • 线程2输入computeHashCode()并暂停一半
  • 线程3以影响哈希码的方式修改对象。
  • 线程1重新启动,将哈希码设置为5并将对象放入哈希映射中作为键。
  • 线程2重新启动,将哈希码设置为78392。

线程1可能会或可能不会在稍后的哈希映射中找到密钥,具体取决于jvm如何选择处理该cachedHashCode。 如果喜欢,jvm可以选择保留非易失性字段的单独副本。 Volatile仅确保jvm不会这样做并且所有线程始终看到相同的值。