线程安全,无易失性
谁能解释为什么这个例子是线程安全的,没有易失性?
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...
}
更多:我明白了,我们不关心该值是否被计算两次(因此它不是真正的线程安全)。我还想知道计算哈希码后创建的新线程是否能保证看到新的哈希码?
Can anyone explain why this example is thread safe without volatile?
http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
In fact, assuming that the computeHashCode function always returned the same result and had no side effects (i.e., idempotent), you could even get rid of all of the synchronization.
// 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...
}
MORE: I get it, we don't care if the value is computed twice (so it is not truly thread safe). I also like to know if new threads created after the hashcode has been calculated is guaranteed to see the new hashcode?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
这是如履薄冰,但这里有解释。可见性问题意味着某些线程可能会看到旧版本,而另一些线程可能会看到新版本。在我们的例子中,一些线程看到
0
,而其他线程则看到cachedHashCode
。调用
hashCode()
并查看cachedHashCode
的线程将仅返回它(if (h == 0)
条件不满足)并且一切正常。但是看到
0
的线程(尽管cachedHashCode
可能已经被计算过)只会再次重新计算。换句话说,在最坏的情况下,每个线程都会进入第一次看到
0
的分支(就像它是ThreadLocal
一样)。由于computeHashCode() 是幂等的(非常重要),多次调用它(通过不同的线程)并将其重新分配给同一个变量不应该有任何副作用。
This is walking on a thin ice, but here is the explanation. Visibility problem means that some threads might see old version and some - new one. In our case, some threads see
0
while others -cachedHashCode
.Threads that call
hashCode()
and seecachedHashCode
will just return it (if (h == 0)
condition is not met) and everything works.But threads that see
0
(despite thecachedHashCode
might have already been computed) will just recompute it again.In other words, in the worst case scenario, every thread will enter the branch seeing
0
for the first time (like if it wasThreadLocal
).Since
computeHashCode()
is idempotent (very important), both calling it several times (by different threads) and reassign it again to the same variable shouldn't have any side effects.这里重要的信息是
如果这是真的,那么computeHashCode 就被认为是有效不可变的,并且由于它始终是相同的值,因此您永远不会遇到并发问题。
至于使cachedHashCode易失性。就线程安全而言,这不会产生任何影响,因为您总是分配并返回一个线程局部变量
h
,它将是一个非零的计算哈希码。The important piece of information here is
If this is true then the computeHashCode is known to be effectively immutable and since it will always be the same value you would never have a concurrency issue.
As far as making cachedHashCode volatile. It wouldnt make a difference as far as thread-safety goes because you are always assigning and returning a thread-local variable
h
which will be a non-zero computedHashCode.这就是所谓的“活泼的单一检查习惯用法”。当计算值是幂等的(每次返回相同的值;推论:类型必须是不可变的)并且便宜(如果不小心重新计算多次也没关系)时,使用它。这总是采取某种形式
,或者或多或少等价的东西。如果您的实现不是幂等的,或者不便宜,那么您应该使用另一种习惯用法;有关详细信息,请参阅有效的 Java 第 71 条。但重点是,线程最多对
cacheField
进行一次读取,如果它们看到cacheField
处于尚未计算值的状态,它们就会重新计算该值。例如,正如《Effective Java》中提到的,这就是
String.hashCode()
的实现方式。This is called the racy single-check idiom. It's used when computing a value is idempotent (returns the same value every time; corollary: the type must be immutable) and cheap (if it's accidentally recomputed more than once that's okay). This always takes some form along the lines of
or something more or less equivalent. If your implementation isn't idempotent, or isn't cheap, then you should use another idiom; see Effective Java item 71 for details. But the point is, threads do at most one read to
cacheField
, and if they seecacheField
in a state where the value hasn't been computed, they recompute the value.As mentioned in Effective Java, this is how
String.hashCode()
is implemented, for example.仅当 Foo 对于贡献哈希码的字段是不可变的时,这才是正确的。 (这是满足“假设computeHashCode函数总是返回相同结果”所必需的)
否则我不同意它是线程安全的。
线程 1 稍后可能能够也可能无法在哈希映射中找到该键,具体取决于 jvm 选择如何处理该 cachedHashCode。如果愿意,jvm 可以选择保留非易失性字段的单独副本。易失性仅确保 jvm 不会这样做,并且所有线程始终看到相同的值。
That would only be true if Foo is immutable with respect to fields that contribute to the hashcode. (which is necessary to satisfy "assuming that the computeHashCode function always returned the same result")
Otherwise I don't agree that it would be threadsafe.
Thread 1 may or may not be able to find the key in the hash map later, depending on how the jvm chose to handle that cachedHashCode. The jvm has the option to keep separate copies of a non-volatile field if it likes. Volatile only ensures that the jvm doesn't do that and that all threads see the same value at all times.