7

我正在通过一个关于 JavaRevisited的面试问题,我很难理解这个问题:

在多线程环境中使用 HashMap 有什么问题?get() 方法何时进入无限循环?

在我看来,在多线程环境中使用不是问题HashMap,只要我们的应用程序不访问/读取正在修改 created 的线程HashMap,而不是简单地访问 HashMap。

因此,正如我所见,只要在应用程序中我们只是HashMap在多线程环境中访问它,就没有问题。

请让我知道我的理解是否正确。

4

4 回答 4

25

在多线程环境中使用 HashMap 有什么问题?get() 方法何时进入无限循环?

让多个线程以不受保护的方式使用非同步集合(实际上是任何可变类)是一个错误。确定如果每个线程都有自己的HashMap 实例,那么这不是问题。如果多个线程添加到同一个 HashMap实例而没有它是一个问题synchronized。即使只有 1 个线程正在修改 aHashMap而其他线程在没有同步的情况下从同一个映射中读取,您也会遇到问题。

如果您需要在多个线程中使用相同的哈希表对象,那么您应该考虑使用ConcurrentHashMap,将每个对 的访问包装HashMap在一个synchronized {}块中,或者使用该Collections.synchronizedMap(new HashMap<...>())构造。

可能get()会进入无限循环,因为其中一个线程只有部分更新的内存视图,HashMap并且必须存在某种对象引用循环。这就是使用具有多个线程的非同步集合的危险。

所以在我的理解中,只要在应用程序中我们只是在多线程环境中访问HashMap就不是问题了?

如果“访问”是指“阅读”,那么这对于quals来说是正确的。您必须确保:

  • 在实例化线程之前HashMap完成对 的所有更新,并且创建映射的线程也分叉线程
  • 线程仅使用HashMap只读模式 - 要么get()迭代,要么不删除
  • 没有线程更新地图

如果这些条件中的任何一个不成立,那么您将需要使用同步地图。

于 2012-06-15T12:26:41.130 回答
3

这是一个经典的问题。ArrayList 和 HashMap 不同步,而 Vector 和 HashTable 是。因此,除非您自己非常小心地定义互斥锁,否则您应该使用 HashTable。

换句话说,例如 HashTable 中的方法将确保在任何给定时间没有其他线程正在使用 HashTable。如果您使用 HashMap,则必须手动执行此操作,方法是确保在调用该方法之前在 HashMap 上进行同步。

更新:查看@Gray 的评论。看起来用 Collections.synchronizedMap(new HashMap()) 包装 HashMap 是现在要走的路。

编辑:其他海报的回答比我好。然而,我的回答引发了关于使用即将被弃用的 Vector、Stack、Hashtable 和 Dictionary 类的有趣讨论,所以我将问题留在这里,作为下面评论的开头。多谢你们!

于 2012-06-15T12:23:20.153 回答
2

我们知道这HashMap是一个非同步的集合,而它的同步对应部分是HashTable. 因此,当您在多线程环境中访问集合并且所有线程都在访问集合的单个实例时,HashTable出于各种明显原因(例如避免脏读和保持数据一致性)使用它会更安全。在最坏的情况下,这种多线程环境也可能导致无限循环。

是的,它是真实的。HashMap.get()可能导致无限循环。让我们看看如何?

如果查看源代码HashMap.get(Object key)方法,它看起来像这样:

 public Object get(Object key) {
    Object k = maskNull(key);
    int hash = hash(k);
    int i = indexFor(hash, table.length);
    Entry e = table[i];
    while (true) {
        if (e == null)
            return e;
        if (e.hash == hash &amp;&amp; eq(k, e.key))
            return e.value;
        e = e.next;
    }
}

while(true){...}在多线程环境中,如果 e.next 可以以某种方式指向自身,则在运行时总是可能成为无限循环的受害者。这将导致无限循环。但是,e.next 将如何指向自身?

这可能发生在void transfer(Entry[] newTable)方法中,该方法在 HashMap 调整大小完成时调用。

    do {
        Entry next = e.next;
        int i = indexFor(e.hash, newCapacity);
        e.next = newTable[i];
    newTable[i] = e;
        e
= next;
} while (e != null);

如果在调整大小的同时,其他线程试图修改地图实例,则这段代码很容易产生上述情况。

避免这种情况的唯一方法是在代码中使用同步,或者更好的是使用同步集合。

于 2020-01-09T04:25:11.300 回答
0

我猜他们的意思是访问HashMap. Shared mutable state.

因为不是synchronized每个线程都会从主内存中获取它的副本,修改并覆盖它。

HashMap with one entry <n, 1>

thread 1 grab the copy

thread 2 grab the copy

thread 1 modify <n, 2>

thread 2 modify <n, 3>

thread 1 is done, and stores the copy in the main memory

now memory is <n, 2>

thread 2 is done and stores the copy

now memory is <n, 3>

The state thread 1 is lost
于 2012-06-15T12:27:56.023 回答