HashMap报错:java.util.ConcurrentModificationException

       HashMap是线程不安全的 ,那么为什么说它是线程不安全的呢?

      首先来看一下官方API有关HashMap线程不安全的描述:

Note that this implementation is not synchronized. If multiple threads access a hash map concurrently, and at least one of the threads modifies the map structurally, itmust be synchronized externally. (A structural modification is any operation that adds or deletes one or more mappings; merely changing the value associated with a key that an instance already contains is not a structural modification.) This is typically accomplished by synchronizing on some object that naturally encapsulates the map. If no such object exists, the map should be "wrapped" using theCollections.synchronizedMap method. This is best done at creation time, to prevent accidental unsynchronized access to the map:

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

The iterators returned by all of this class's "collection viewmethods" arefail-fast: if the map is structurally modified at anytime after the iterator is created, in any way except through the iterator'sown remove method, the iterator will throw aConcurrentModificationException. Thus, in theface of concurrent modification, the iterator fails quickly and cleanly, ratherthan risking arbitrary, non-deterministic behavior at an undetermined time inthe future.

Note that the fail-fast behavior of an iterator cannotbe guaranteed as it is, generally speaking, impossible to make any hardguarantees in the presence of unsynchronized concurrent modification. Fail-fastiterators throw ConcurrentModificationException on a best-effort basis.Therefore, it would be wrong to write a program that depended on this exceptionfor its correctness: the fail-fast behavior of iterators should be used onlyto detect bugs.

    这段文字大致的翻译::

   

注意,该实现不是同步的。如果多个线程同时访问一个哈希映射,并且至少有一个线程从结构上修改了该映射,则必须 保持同步。(结构上的修改是指添加或删除一个或多个映射关系的任何操作;仅改变与实例已经包含的键关联的值不是结构上的修改。)一般通过对自然封装该映射的对象进行同步操作来完成。如果不存在这样的对象,则应该使用 Collections.synchronizedMap 方法来“包装”该映射。最好在创建时完成这一操作,避免对映射进行意外的非同步访问,如下所示:

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

由所有此类的“collection 视图方法”所返回的迭代器都是快速失败 的:在迭代器创建之后,如果从结构上对映射进行修改,除非通过迭代器本身的 remove 方法,其他任何时间任何方式的修改,迭代器都将抛出ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不冒在将来不确定的时间发生任意不确定行为的风险。

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

测试代码如下:

import java.util.*;  
public class Main  
{  
	public static void main(String args[])  
	{  
		try {
			HashMap<String, String> hashMap = new HashMap<String, String>();
			hashMap.put("1", "Hello");  
			hashMap.put("2", "World");  
			//			bb.remove("1");  //直接删除的方式 不会报错
			Iterator<String> it = hashMap.keySet().iterator();  
			while(it.hasNext()) {  
				Object ele = it.next(); 
				System.out.println(hashMap);
				if (ele.equals("1")) {
					//hashMap.remove(ele);    //出错 修改了映射结构 影响了迭代器遍历
					it.remove();              //用迭代器删除 则不会出错
				}
			} 

		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}

	}  

}  
其中:

hashMap.remove(ele);

报错如下:


与文档描述一致。

                再来描述一下,我遇到的问题。

                有三个线程,线程A查询配置文件,把结果存放到HashMap容器,每次存放查询结果之前,把上次的结果清除了,即调用了HashMap的clear()函数。综合起来,在线程A做的事情就是清除容器的结果 ,然后存放新的结果。代码截图如下:

            

         另一个线程B,则使用该配置文件的查询结果,做一些数据处理,代码如下:

 


              另一个线程C,也是使用该配置文件的查询结果,做一些数据处理,代码如下:


某一天查询日志 ,发现线程B会不定期出现如下截图的报错:

  

       发现线程C会不定期出现如下截图的报错:     



        很奇怪呢,根据官网描述,我并没有删除元素HashMap的元素,更没有修改HashMap的结构,也是我猜测难道是在线程A的第6行代码进行清除操作时,同时线程B的第367行代码在进行赋值操作,或者线程C的第112行代码在进行赋值操作,导致多个线程同时操作HashMap,出现异常,于是把线程A修改为如下方式:

            


               修改的想法是, 这样修改,一方面解决了,线程B会读取到空的HashMap的情况(线程A刚清除了结果,线程B就去读取,导致读的结果为空),同时也解决了,多线程访问HashMap出现的报错问题。让人心累的是,第二天查看日志,又出现了同样的报错。

               最后的解决办法是把HashMap修改为ConcurrentHashMap,才解决了问题。









       





猜你喜欢

转载自blog.csdn.net/dyingfair/article/details/56481352
今日推荐