如果你是一名Java开发人员,我能够确定你肯定知道ConcurrentModificationException,它是在使用迭代器遍历集合对象时修改集合对象造成的(并发修改)异常。实际上,Java的集合框架是迭代器设计模式的一个很好的实现。

Java 1.5引入了java.util.concurrent包,其中Collection类的实现允许在运行过程中修改集合对象。

ConcurrentHashMap是一个与HashMap很相似的类,但是它支持在运行时修改集合对象。

让我们通过一个简单的程序来帮助理解:

ConcurrentHashMapExample.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.journaldev.util;
 
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
 
public class ConcurrentHashMapExample {
 
    public static void main(String[] args) {
 
        //ConcurrentHashMap
        Map<String,String> myMap = new ConcurrentHashMap<String,String>();
        myMap.put("1", "1");
        myMap.put("2", "1");
        myMap.put("3", "1");
        myMap.put("4", "1");
        myMap.put("5", "1");
        myMap.put("6", "1");
        System.out.println("ConcurrentHashMap before iterator: "+myMap);
        Iterator<String> it = myMap.keySet().iterator();
 
        while(it.hasNext()){
            String key = it.next();
            if(key.equals("3")) myMap.put(key+"new", "new3");
        }
        System.out.println("ConcurrentHashMap after iterator: "+myMap);
 
        //HashMap
        myMap = new HashMap<String,String>();
        myMap.put("1", "1");
        myMap.put("2", "1");
        myMap.put("3", "1");
        myMap.put("4", "1");
        myMap.put("5", "1");
        myMap.put("6", "1");
        System.out.println("HashMap before iterator: "+myMap);
        Iterator<String> it1 = myMap.keySet().iterator();
 
        while(it1.hasNext()){
            String key = it1.next();
            if(key.equals("3")) myMap.put(key+"new", "new3");
        }
        System.out.println("HashMap after iterator: "+myMap);
    }
 
}

当我们试着运行上面的程序,输出如下:

1
2
3
4
5
6
7
ConcurrentHashMap before iterator: {1=1, 5=1, 6=1, 3=1, 4=1, 2=1}
ConcurrentHashMap after iterator: {1=1, 3new=new3, 5=1, 6=1, 3=1, 4=1, 2=1}
HashMap before iterator: {3=1, 2=1, 1=1, 6=1, 5=1, 4=1}
Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.HashMap$HashIterator.nextEntry(HashMap.java:793)
    at java.util.HashMap$KeyIterator.next(HashMap.java:828)
    at com.test.ConcurrentHashMapExample.main(ConcurrentHashMapExample.java:44)

查看输出,很明显ConcurrentHashMap可以支持向map中添加新元素,而HashMap则抛出了ConcurrentModificationException。

查看异常堆栈记录,可以发现是下面这条语句抛出异常:

1
String key = it1.next();

这就意味着新的元素在HashMap中已经插入了,但是在迭代器执行时出现错误。事实上,集合对象的迭代器提供快速失败(Fail-Fast)的机制,即修改集合对象结构或者元素数量都会使迭代器触发这个异常。

但是迭代器是怎么知道HashMap被修改了呢,我们可以一次取出HashMap的所有Key然后进行遍历。

HashMap包含一个修改计数器,当你调用它的next()方法来获取下一个元素时,迭代器将会用到这个计数器。

HashMap.java

1
2
3
4
5
6
7
/**
 * HashMap结构的修改次数
 * 结构修改是指:改变了HashMap中mapping的个数或者其中的内部结构(比如,重新计算hash值)
 * 这个字段在通过Collection操作Hashmap时提供快速失败(Fail-fast)功能。
 * (参见 ConcurrentModificationException)。
 */
transient volatile int modCount;

现在为了证明上面的观点,我们对原来的代码做一点修改,使迭代器在插入新的元素后跳出循环。只要在调用put方法后增加一个break:

1
2
3
4
if(key.equals("3")){
    myMap.put(key+"new", "new3");
    break;
}

再执行修改后的代码,会得到下面的输出结果:

1
2
3
4
ConcurrentHashMap before iterator: {1=1, 5=1, 6=1, 3=1, 4=1, 2=1}
ConcurrentHashMap after iterator: {1=1, 3new=new3, 5=1, 6=1, 3=1, 4=1, 2=1}
HashMap before iterator: {3=1, 2=1, 1=1, 6=1, 5=1, 4=1}
HashMap after iterator: {3=1, 2=1, 1=1, 3new=new3, 6=1, 5=1, 4=1}

最后,如果我们不添加新的元素而是修改已经存在的键值对会不会抛出异常呢?

修改原来的程序并且自己验证一下:

1
2
//myMap.put(key+"new", "new3");
myMap.put(key, "new3");

如果你对于输出结果感觉困惑或者震惊,在下面评论。我会很乐意给出进一步解释。

你有没有注意到那些我们在创建集合和迭代器时的尖括号,在Java中这叫做泛型,当涉及到编译时的类型检查和去除运行时的ClassCastException的时候会很有帮助。点击这里可以了解更多泛型教程。

原文链接: journaldev 翻译: ImportNew.com风恋星
译文链接: http://www.importnew.com/8162.html
转载请保留原文出处、译者和译文链接。]

HashMap vs ConcurrentHashMap — 示例及Iterator探秘的更多相关文章

  1. 沉淀再出发:java中的HashMap、ConcurrentHashMap和Hashtable的认识

    沉淀再出发:java中的HashMap.ConcurrentHashMap和Hashtable的认识 一.前言 很多知识在学习或者使用了之后总是会忘记的,但是如果把这些只是背后的原理理解了,并且记忆下 ...

  2. 轻松理解 Java HashMap 和 ConcurrentHashMap

    前言 Map 这样的 Key Value 在软件开发中是非常经典的结构,常用于在内存中存放数据. 本篇主要想讨论 ConcurrentHashMap 这样一个并发容器,在正式开始之前我觉得有必要谈谈 ...

  3. 高并发第九弹:逃不掉的Map --> HashMap,TreeMap,ConcurrentHashMap

    平时大家都会经常使用到 Map,面试的时候又经常会遇到问Map的,其中主要就是 ConcurrentHashMap,在说ConcurrentHashMap.我们还是先看一下, 其他两个基础的 Map ...

  4. 深入理解HashMap和concurrentHashMap

    原文链接:https://segmentfault.com/a/1190000015726870 前言 Map 这样的 Key Value 在软件开发中是非常经典的结构,常用于在内存中存放数据. 本篇 ...

  5. HashMap与ConcurrentHashMap、HashTable

    (1)HashMap的线程不安全原因一:死循环 原因在于HashMap在多线程情况下,执行resize()进行扩容时容易造成死循环. 扩容思路为它要创建一个大小为原来两倍的数组,保证新的容量仍为2的N ...

  6. JAVA HashMap与ConcurrentHashMap

    HashMap Fast-Fail(遍历时写入操作异常) 在使用迭代器的过程中如果HashMap被修改,那么ConcurrentModificationException将被抛出,也即Fast-fai ...

  7. java并发编程笔记(十)——HashMap与ConcurrentHashMap

    java并发编程笔记(十)--HashMap与ConcurrentHashMap HashMap参数 有两个参数影响他的性能 初始容量(默认为16) 加载因子(默认是0.75) HashMap寻址方式 ...

  8. JAVA学习:HashMap 和 ConcurrentHashMap

     一.最基本的HashMap 和 ConcurrentHashMap 1.HashMap的结构和底层原理:由数组和链表组成,数组里面每个地方都存了Key-Value这样的实例,在Java7叫Entry ...

  9. HashMap?ConcurrentHashMap?

    Java团长在csdn 2019-02-06 19:58:00  104241  收藏 666     来源:crossoverJie(ID:crossoverJie) 前言 Map 这样的 Key ...

随机推荐

  1. shell细节决定高度

    1.文件测试操作符 -f[file]:文件存在且为普通文件则为真; -d[directory]:文件存在且为目录文件则为真; -s[size]:文件存在且为文件大小不为0则为真; -e[exist]: ...

  2. CDOJ_149 解救小Q

    原题网址:http://acm.uestc.edu.cn/#/problem/show/149 小Q被邪恶的大魔王困在了迷宫里,love8909决定去解救她. 迷宫里面有一些陷阱,一旦走到陷阱里,就会 ...

  3. XSY1659 [HNOI2012]永无乡

    题面 Description 永无乡包含 n 座岛,编号从 1 到 n. 每座岛都有自己的独一无二的重要度,按照重要度可以将这n座岛排名,名次用 1到n来表示.某些岛之间由巨大的桥连接,通过桥可以从一 ...

  4. oracle 树查询

    select LPAD('-----',t.menu_level)||t.obj_id,t.*,rowid from imes10dba.tb_adm_menu t start with t.pare ...

  5. dubbo常见问题解答FAQ

    常见问题解答 1. 如果服务注册不上怎么办? 2. 出现RpcException: No provider available for remote service异常怎么办? 3. 出现调用超时co ...

  6. 现代数字信号处理——AR模型

    1. AR模型概念观       AR模型是一种线性预测,即已知N个数据,可由模型推出第N点前面或后面的数据(设推出P点),所以其本质类似于插值,其目的都是为了增加有效数据,只是AR模型是由N点递推, ...

  7. python的私有化

    1.双下划线(__) 由双下划线開始的属性在执行时被混淆,所以直接訪问是不同意的.实际上以双下划线開始的属性 会在解释时在前面加上下划线和类名.如self.__num会被解析为self._classN ...

  8. scheme语言编写执行

    scheme是lisp的一种 编辑器能够用emacs.网上有非常多教导怎样编写的 (begin (display "hello") (newline)) 编写完以.scm保存,这里 ...

  9. CString和string头文件

    在使用了MFC库的工程中CString可以直接使用,在没有使用MFC库的工程中加入#include <atlstr.h> 要使用STL里的string,要加入#include <st ...

  10. Qt5的插件机制(6)--开发Qt插件时几个重要的宏

    怎样开发Qt插件,能够在Qt Assistant 中搜索"Qt Plugins"或"How to Create Qt Plugins",看看那篇manual中的 ...