Java HashMap竞争条件

时间:2011-10-20 03:25:58

标签: java multithreading synchronization race-condition

我试图找出这段代码中是否会出现任何竞争条件。如果密钥不是'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());
     }
}

5 个答案:

答案 0 :(得分:13)

答案是肯定的,有潜在的竞争条件:

  • 当两个线程同时调整 HashMap时
  • 发生碰撞时。当两个元素映射到同一个单元格时即使它们具有不同的哈希码,也会发生冲突。在冲突解决期间,可能存在竞争条件,并且一个添加的键/值对可能被另一个线程插入的另一对覆盖。

为了更好地解释我在第二点上的意思,我正在查看source code of HashMap in OpenJdk 7

389        int hash = hash(key.hashCode());
390        int i = indexFor(hash, table.length);

首先计算密钥的哈希值(组合两个哈希函数),然后映射到具有indexFor的单元格,然后检查该单元格是否包含相同的密钥或已被另一个密钥占用。如果它是相同的键,它只是覆盖该值,这里没有问题。

如果它被占用它会查看下一个单元格,然后查看下一个单元格,直到它找到一个空位置并调用addEntry(),如果数组比某个{{1}更加负载,它甚至可以决定调整数组的大小}}。

我们包含条目的loadFactor只是table的向量,它包含键和值。

Entry

在并发环境中,所有类型的恶意事件都可能发生,例如,一个线程获取5号单元格的冲突并查找下一个单元格(6)并将其查找为空。

同时另一个线程因146 /** 147 * The table, resized as necessary. Length MUST Always be a power of two. 148 */ 149 transient Entry[] table; 而获得索引6,并且两者都决定同时使用该单元格,其中一个会覆盖另一个。

答案 1 :(得分:2)

在没有深入了解Hashmap实现的具体细节的情况下,我会说存在错误的可能性,因为Hashmap类对于并发访问是不安全的。

虽然我同意一次只对单个密钥进行一次修改,因为您使用的是currentThread(),但仍有可能多个线程同时修改Hashmap 。除非你看一下具体的实现,否则你不应该假设只有对同一个密钥的并发访问会导致Hashmap出现问题,并且不会对不同的密钥进行并发修改。

想象一下两个不同的密钥生成相同的哈希值的情况,并且很容易看出并发修改时仍然存在错误。

答案 2 :(得分:1)

是的,这不是一件安全的事情(正如其他答案已经指出的那样)。一个更好的解决方案可能是使用ThreadLocal,这是一种比使用Map更自然的方式来保存线程本地数据。它有一些很好的功能,包括默认值,并在线程终止时删除这些值。

答案 3 :(得分:0)

根据Pierre Hugues编写的article,如果你在多个线程之间共享hashmap,你的进程可能因为无限循环而挂起并占用你所有的cpu资源。

答案 4 :(得分:0)

我同意以前的答案,即您的代码不是线程安全的,而使用ConcurrentHashMap可以解决您的问题,这是ThreadLocal的完美用例。

ThreadLocal的简短介绍:

ThreadLocal将在内部为访问ThreadLocal的每个线程保存一个类的不同实例,从而解决任何并发问题。另外(取决于情况可能是好/坏),存储在ThreadLocal中的值只能由首先填充该值的线程访问。如果这是当前线程第一次访问ThreadLocal,则该值将为null。

包含String值的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();
    }
}