执行HashMap操作时为什么ExecutorService会死锁?

Ste*_*hen 5 java concurrency hashmap

运行以下类时,ExecutionService通常会死锁.

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


public class ExecutorTest {
public static void main(final String[] args) throws InterruptedException {
    final ExecutorService executor = Executors.newFixedThreadPool(10);

    final HashMap<Object, Object> map = new HashMap<Object, Object>();
    final Collection<Callable<Object>> actions = new ArrayList<Callable<Object>>();
    int i = 0;
    while (i++ < 1000) {
        final Object o = new Object();
        actions.add(new Callable<Object>() {
            public Object call() throws Exception {
                map.put(o, o);
                return null;
            }
        });
        actions.add(new Callable<Object>() {
            public Object call() throws Exception {
                map.put(new Object(), o);
                return null;
            }
        });
        actions.add(new Callable<Object>() {
            public Object call() throws Exception {
                for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) {
                    iterator.next();
                }
                return null;
            }
        });
    }
    executor.invokeAll(actions);
    System.exit(0);
}

}
Run Code Online (Sandbox Code Playgroud)

那么为什么会这样呢?或者更好 - 我如何编写测试以确保自定义抽象映射的实现是线程安全的?(某些实现具有多个映射,另一个实现具有缓存实现等)

一些背景:这发生在Windows上的Java 1.6.0_04和1.6.0_07下.我知道问题来自sun.misc.Unsafe.park():

  • 我可以在我的Core2 Duo 2.4Ghz笔记本电脑上重现这个问题,但在调试中运行则不行
  • 我可以在我的Core2 Quad上进行调试,但是我把它挂在了RDP上,因此在明天之前无法获得堆栈跟踪

下面的大多数答案都是关于HashMap的非线程安全性,但我在HashMap中找不到锁定的线程 - 它全部在ExecutionService代码中(和Unsafe.park()).我明天会仔细研究这些话题.

所有这一切都是因为自定义抽象Map实现不是线程安全的,所以我开始确保所有实现都是线程安全的.本质上,我想确保我对ConcurrentHashMap等的理解正是我所期望的,但却发现ExecutionService奇怪地缺乏......

Rob*_*anu 16

您正在使用一个众所周知的非线程安全类并抱怨死锁.我没有看到这里的问题.

另外,怎么样? ExecutionService

strangely lacking
Run Code Online (Sandbox Code Playgroud)

这是一个常见的误解,通过使用例如一个HashMap,你会在最得到一些陈旧的数据.见一个漂亮的竞争条件你怎么能这样做只是炸毁你的JVM.

理解为什么会发生这种情况是一个非常棘手的过程,需要了解JVM和类库的内部结构.

至于ConcurrentHashMap,只需阅读javadoc - 它应该澄清你的问题.如果没有,请查看Java Concurrency in Practice.


更新:

我设法重现你的情况,但这并不是一个僵局.其中一个actions永远不会完成执行.堆栈跟踪是:

"pool-1-thread-3" prio=10 tid=0x08110000 nid=0x22f8 runnable [0x805b0000]
java.lang.Thread.State: RUNNABLE
at ExecutorTest$3.call(ExecutorTest.java:36)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
at java.util.concurrent.FutureTask.run(FutureTask.java:138)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
 at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:619)
Run Code Online (Sandbox Code Playgroud)

它看起来像我链接到的确切情况 - HashMap被调整大小,并且由于调整迭代器大小的内部机制被卡在无限循环中.

发生这种情况时,invokeAll永远不会返回并且程序挂起.但它既不是僵局,也不是​​活锁,而是竞争条件.