HashMap和可见性

ass*_*ias 14 java multithreading hashmap

HashMap的javadoc声明:

如果在创建迭代器之后的任何时候对映射进行结构修改,除了通过迭代器自己的remove方法之外,迭代器将抛出ConcurrentModificationException.

我构建了一个示例代码,根据规范,它应该几乎立即失败并抛出ConcurrentModificationException;

  • 它会像Java 7那样立即失败
  • 但它(似乎)始终使用Java 6(即它不会抛出承诺的异常).

注意:它有时不会因Java 7而失败(比如20次中的1次) - 我猜它与线程调度有关(即2个runnables不是交错的).

我错过了什么吗?为什么使用Java 6运行的版本不会抛出ConcurrentModificationException?

实质上,有2个Runnable任务并行运行(倒计时器用于使它们大约同时启动):

  • 一个是向地图添加项目
  • 另一个是在地图上迭代,读取键并将它们放入数组中

然后主线程检查已添加到阵列的键数.

Java 7典型输出(迭代立即失败):

java.util.ConcurrentModificationException
MAX i = 0

Java 6典型输出(整个迭代通过,数组包含所有添加的键):

MAX i = 99

使用的代码:

public class Test1 {

    public static void main(String[] args) throws InterruptedException {
        final int SIZE = 100;
        final Map<Integer, Integer> map = new HashMap<Integer, Integer>();
        map.put(1, 1);
        map.put(2, 2);
        map.put(3, 3);
        final int[] list = new int[SIZE];
        final CountDownLatch start = new CountDownLatch(1);
        Runnable put = new Runnable() {
            @Override
            public void run() {
                try {
                    start.await();
                    for (int i = 4; i < SIZE; i++) {
                        map.put(i, i);
                    }
                } catch (Exception ex) {
                }
            }
        };

        Runnable iterate = new Runnable() {
            @Override
            public void run() {
                try {
                    start.await();
                    int i = 0;
                    for (Map.Entry<Integer, Integer> e : map.entrySet()) {
                        list[i++] = e.getKey();
                        Thread.sleep(1);
                    }
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        };
        ExecutorService e = Executors.newFixedThreadPool(2);
        e.submit(put);
        e.submit(iterate);
        e.shutdown();

        start.countDown();
        Thread.sleep(100);
        for (int i = 0; i < SIZE; i++) {
            if (list[i] == 0) {
                System.out.println("MAX i = " + i);
                break;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

注意:在x86计算机上使用JDK 7u11和JDK 6u38(64位版本).

And*_*niy 9

如果我们将查看HashMap源代码并在Java 6和Java 7之间进行比较,我们将看到如此有趣的区别:

transient volatile int modCount;在Java6中,只transient int modCount;在Java7中.

我确信这是导致所提到的代码的不同行为的原因:

        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
Run Code Online (Sandbox Code Playgroud)

UPD:在我看来,这是一个已知的Java 6/7错误:http : //bugs.sun.com/bugdatabase/view_bug.do?bug_id=6625725,它已在最新的Java7中修复.

UPD-2: @Renjith先生说,他刚刚测试过,并没有发现HashMaps实施的行为有任何差异.但我也测试了它.

我的测试是:

1)我创建了HashMap2类,它绝对是HashMap Java 6的副本.

一件重要的事情是我们需要在这里介绍2个新领域:

transient volatile Set<K>        keySet = null;
Run Code Online (Sandbox Code Playgroud)

transient volatile Collection<V> values = null;
Run Code Online (Sandbox Code Playgroud)

2)然后我HashMap2在测试这个问题时使用它并在Java 7下运行它

结果:它就像这样的测试Java 6,即没有任何ConcurentModificationException.

这一切都证明了我的猜想.QED


jta*_*orn 6

作为旁注,ConcurrentModificationException(尽管名称不幸)并不是为了检测跨多个线程的修改.它用于捕获单个线程内的修改.无论使用迭代器还是其他任何东西,都可以保证在多个线程之间修改共享HashMap的效果(没有正确的同步).

简而言之,无论jvm版本如何,您的测试都是假的,只有"运气"才能完成任何不同的操作.例如,由于在跨线程查看时HashMap内部处于不一致状态,此测试可能会抛出NPE或其他一些"不可能"的异常.