Java HashMap 竞争条件
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/7830791/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Java HashMap race condition
提问by JavaLearner
I am trying to find out if there is going to be any race condition in this piece of code. If the key weren't 'Thread.currentThread' then I would think yes. But since the thread itself is key, how is it possible to have a race condition? No other thread can possibly update the same key in the HashMap!
我试图找出这段代码中是否会有任何竞争条件。如果关键不是 'Thread.currentThread' 那么我会认为是的。但是既然线程本身是关键,那么怎么可能有竞争条件呢?没有其他线程可以更新 HashMap 中的相同键!
public class SessionTracker {
static private final Map<Thread,Session> threadSessionMap = new HashMap<Thread,Session>();
static public Session get() {
return threadSessionMap.get(Thread.currentThread());
}
static public void set(Session s) {
threadSessionMap.put(Thread.currentThread(),s);
}
static public void reset() {
threadSessionMap.remove(Thread.currentThread());
}
}
回答by stivlo
The answer is yes, there are potential race conditions:
答案是肯定的,存在潜在的竞争条件:
- when resizingan HashMap by two threads at the same time
- when collisionshappens. Collision can happen when two elements map to the same cell even if they have a different hashcode. During the conflict resolution, there can be a race condition and one added key/value pair could be overwritten by another pair inserted by another thread.
- 同时通过两个线程调整HashMap 的大小时
- 当碰撞发生时。当两个元素映射到同一个单元格时,即使它们具有不同的哈希码,也会发生冲突。在冲突解决期间,可能存在竞争条件,并且一个添加的键/值对可能被另一个线程插入的另一对覆盖。
To explain better what I mean on the second point, I was looking at the source code of HashMap in OpenJdk 7
为了更好地解释我在第二点的意思,我正在查看OpenJdk 7 中 HashMap的源代码
389 int hash = hash(key.hashCode());
390 int i = indexFor(hash, table.length);
First it calculates an Hash of your key (combining two hash functions), then it maps to a cell with indexFor
, then it checks if that cell contains the same key or is already occupied by another one. If it's the same key, it just overwrite the value and there is no problem here.
首先它计算你的键的哈希(结合两个哈希函数),然后它映射到一个单元格indexFor
,然后检查该单元格是否包含相同的键或已经被另一个键占用。如果是相同的键,它只是覆盖该值,这里没有问题。
If it's occupied it looks at the next cell and then the next until it finds an empty position and call addEntry()
, which could even decide to resize the array if the array is more loaded than a certain loadFactor
.
如果它被占用,它会查看下一个单元格,然后查看下一个单元格,直到找到一个空位置并调用addEntry()
,如果数组比某个加载的更多,它甚至可以决定调整数组的大小loadFactor
。
Our table
containing the entries is just a vector of Entry
which holds key and value.
我们table
包含的条目只是一个Entry
包含键和值的向量。
146 /**
147 * The table, resized as necessary. Length MUST Always be a power of two.
148 */
149 transient Entry[] table;
In a concurrent environment, all sort of evil things can happen, for instance one thread gets a collision for cell number 5 and looks for the next cell (6) and finds it empty.
在并发环境中,各种邪恶的事情都可能发生,例如一个线程与单元格 5 发生冲突并寻找下一个单元格 (6) 并发现它是空的。
Meanwhile another thread gets an index of 6 as a result of indexFor
and both decide to use that cell at the same time, one of the two overwriting the other.
同时,另一个线程的索引为 6,结果indexFor
两者都决定同时使用该单元格,两个线程中的一个覆盖另一个。
回答by Steven Mastandrea
Without getting into specific details of the Hashmap implementations, I would say that there is still the possibility of an error, given the fact that the Hashmap class is not safe for concurrent access.
没有深入了解 Hashmap 实现的具体细节,我想说仍然存在错误的可能性,因为 Hashmap 类对于并发访问是不安全的。
While I agree that there should be only 1 modification to a single keyat a time, because you are using currentThread(), there is still the possibility that multiple threads will be modifying the Hashmap concurrently. Unless you look at the specific implementation, you should not assume that only concurrent access to the same key would cause a problem on the Hashmap, and that concurrent modification to different keys would not.
虽然我同意一次应该只对单个键进行1 次修改,因为您使用的是 currentThread(),但仍有可能多个线程同时修改 Hashmap。除非你看具体的实现,否则你不应该假设只有并发访问同一个键会导致Hashmap出现问题,而对不同键的并发修改不会。
Imagine a case where two different keys generate to the same hash value, and its easy to see that there can still be errors with concurrent modification.
想象一个情况,两个不同的键生成相同的哈希值,很容易看出并发修改仍然会出错。
回答by Steven Schlansker
Yes, that is not a safe thing to do (as the other answers already point out). A better solution entirely might be to use a ThreadLocalwhich is a more natural way to keep thread local data than using a Map. It's got a couple of nice features including default values and that the values are removed when a thread terminates.
是的,这不是一件安全的事情(正如其他答案已经指出的那样)。一个更好的解决方案可能是使用ThreadLocal,这是一种比使用 Map 更自然的保持线程本地数据的方法。它有几个不错的特性,包括默认值,并且在线程终止时删除这些值。
回答by okwap
回答by stikku
I agree with previous answers that your code is not thread safe and while using ConcurrentHashMap would solve your problem, this is the perfect use case for ThreadLocal.
我同意之前的答案,即您的代码不是线程安全的,虽然使用 ConcurrentHashMap 可以解决您的问题,但这是ThreadLocal的完美用例。
A short introduction for ThreadLocal:
ThreadLocal 的简短介绍:
ThreadLocal will internally hold a different instance of a class for each thread that accesses the ThreadLocal, therefor solving any concurrency issues. Additionally (depending on situation this could be good/bad), the value stored in a ThreadLocal can only be accessed by the thread that populated that value in the first place. If it is the first time the current thread is accessing ThreadLocal, the value will be null.
ThreadLocal 将在内部为每个访问 ThreadLocal 的线程保存一个类的不同实例,从而解决任何并发问题。此外(根据情况这可能是好是坏),存储在 ThreadLocal 中的值只能由首先填充该值的线程访问。如果是当前线程第一次访问 ThreadLocal,则该值将为空。
Simple example of ThreadLocal that holds String values:
保存字符串值的 ThreadLocal 的简单示例:
private static ThreadLocal<String> threadVar = new ThreadLocal<>();
public void foo() {
String myString = threadVar.get();
if (myString == null) {
threadVar.set("some new value");
myString = threadVar.get();
}
}