为什么代码会挂起来自多个线程的HashMap.put()?

时间:2014-04-08 18:08:14

标签: java concurrency hashmap

我一直试图通过创建一个将值放到地图上的简单单元测试来证明应用程序中存在错误。我期待ConcurrentModificationException,但我所得到的只是在执行者身上悬挂线程,我不知道问题到底在哪里。

测试在这里:

@Test
public void testHashMap() throws Exception {
    final Random rnd = new Random();
    final Map<String, Object> map = new HashMap<>();
    ExecutorService executor = Executors.newFixedThreadPool(10);
    for (int i = 0; i < 100; i++) {
        final int counter=i;
        executor.execute(new Runnable() {
            @Override
            public void run() {
                try{
                    for (int j = 0; j<1000; j++){
                        map.put(String.valueOf(rnd.nextLong()), new Object());
                        //map.put("A", new Object());
                    }
                    System.out.println("Thread "+counter+" finished");
                }catch(Exception e){
                    System.out.println("Thread "+counter+" failed with exception: ");
                    e.printStackTrace();
                }
            }
        });
    }
    executor.shutdown();
    int i = 0;
    while (!executor.isTerminated()) {
        i++;
        Thread.sleep(1000);
        System.out.println("Waited "+i+" seconds");
    }
}

我知道我不应该这样做,但我不明白为什么我没有得到例外以及为什么线程只是挂在那里?当我在地图上做一个简单的put(注释代码)时,它会很好地传递。

以下是示例输出:

Thread 0 finished
Thread 1 finished
Thread 4 finished
Thread 2 finished
Thread 5 finished
Thread 7 finished
Thread 9 finished
Thread 10 finished
Thread 13 finished
Thread 6 finished
Thread 14 finished
Thread 8 finished
Thread 12 finished
Thread 16 finished
Thread 19 finished
Thread 20 finished
Thread 21 finished
Thread 26 finished
Thread 25 finished
Thread 24 finished
Thread 28 finished
Thread 3 finished
Thread 31 finished
Thread 30 finished
Thread 32 finished
Thread 34 finished
Thread 35 finished
Thread 36 finished
Thread 37 finished
Thread 38 finished
Thread 39 finished
Thread 22 finished
Thread 27 finished
Thread 42 finished
Thread 43 finished
Thread 41 finished
Thread 45 finished
Thread 44 finished
Thread 47 finished
Thread 48 finished
Thread 49 finished
Waited 1 seconds
Waited 2 seconds
Waited 3 seconds
Waited 4 seconds
Waited 5 seconds
...indefinitely

3 个答案:

答案 0 :(得分:8)

  

为什么代码会挂起来自多个线程的HashMap.put()?

如果没有外部同步,则不能将HashMap与多个线程一起使用。您应该切换为使用ConcurrentHashMap

您也可以使用Collections.synchronizedMap(new HashMap<>());,但ConcurrentHashMap可以提供更好的效果。

  

我期待ConcurrentModificationException,但我得到的只是在执行程序中悬挂线程,我不知道问题到底在哪里。

您看到一个挂起,因为其中一个线程的HashMap版本已损坏 - 可能是一个循环链接列表,其中两个散列条目相互链接。如果你进行线程转储,你会看到它在遍历HashMap条目时正在旋转。

ConcurrentModificationException仅在HashMap检测到修改时才会引发map.remove(...)。通常,当您(例如)在迭代地图时调用iterator.remove()而不是HashMap删除条目时,这是单线程使用。

同步有两个重要的事项:互斥锁定和内存同步。每个处理器都有自己的内存缓存,线程可以轻松查看对象的部分同步视图(在这种情况下为{{1}}),而没有正确的内存同步。

  

唯一令人沮丧的是有时会引发异常,但大部分时间它都会挂起。

在多线程情况下,根据定义存在大量竞争条件,因为线程通常在多个处理器上并行运行。考虑到环境的平行性,很难预测失败的类型。

答案 1 :(得分:3)

引用HashMap的文档:

  

请注意,迭代器的快速失败行为无法得到保证,因为一般来说,在存在非同步并发修改的情况下,不可能做出任何硬性保证。失败快速迭代器尽最大努力抛出ConcurrentModificationException。因此,编写一个依赖于此异常的程序以确保其正确性是错误的:迭代器的快速失败行为应仅用于检测错误。

继续推荐:

Map m = Collections.synchronizedMap(new HashMap(...));

确保您的地图已同步。

http://docs.oracle.com/javase/7/docs/api/java/util/HashMap.html#put(K,V)

答案 2 :(得分:1)

HashMap不是线程安全的。您看到的问题正是我在生产应用程序中看到的几次(不幸的是......)。会发生什么是HashMap.put需要对内部数据结构进行更改。您可以在逻辑上将其视为一个操作:将对象放入地图中。但在幕后,可能需要进行各种管理,例如重新调整内部哈希表的大小。

这需要以原子方式完成。 put方法可能需要读取数据,然后根据找到的内容进行更新。如果两个线程同时进行,它们就会踩在彼此的脚上。想象一下,如果其中一个读取涉及循环,例如循环遍历散列表桶中的对象。如果一个线程在另一个线程重新排列表时循环它,它可能会以无限循环结束。

长话短说:使用同步块或Hashtable