`
cuisuqiang
  • 浏览: 3962759 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
3feb66c0-2fb6-35ff-968a-5f5ec10ada43
Java研发技术指南
浏览量:3672131
社区版块
存档分类
最新评论

Map使用中的问题 异常java.util.ConcurrentModificationException

    博客分类:
  • J2EE
阅读更多

我想对数据访问做一个缓冲,选用Map来做缓冲容器,考虑到效率我选择了HashMap

 

想想循环往里面仍或者更新数据,那么当系统不访问的时候这些内容,我应该实时的清除这些内存内容

 

根据需要,我写了一个静态Map做内存容器,然后设置一个Spring定时器来定时检查和处理那些数据需要清除

但是定时器处理时遇到异常 java.util.ConcurrentModificationException ,遇到线程安全问题

 

查了一下HashMap的API介绍:

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

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

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

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

 

 怎么解决呢?

找了一下API,在1.5后有这样一个Map对象ConcurrentHashMap

介绍如下:

支持获取的完全并发和更新的所期望可调整并发的哈希表。此类遵守与 Hashtable 相同的功能规范,并且包括对应于 Hashtable 的每个方法的方法版本。不过,尽管所有操作都是线程安全的,但获取操作 必锁定,并且 支持以某种防止所有访问的方式锁定整个表。此类可以通过程序完全与 Hashtable 进行互操作,这取决于其线程安全,而与其同步细节无关。

获取操作(包括 get)通常不会受阻塞,因此,可能与更新操作交迭(包括 putremove)。获取会影响最近完成的 更新操作的结果。对于一些聚合操作,比如 putAllclear,并发获取可能只影响某些条目的插入和移除。类似地,在创建迭代器/枚举时或自此之后,Iterators 和 Enumerations 返回在某一时间点上影响哈希表状态的元素。它们不会 抛出 ConcurrentModificationException。不过,迭代器被设计成每次仅由一个线程使用。

这允许通过可选的 concurrencyLevel 构造方法参数(默认值为 16)来引导更新操作之间的并发,该参数用作内部调整大小的一个提示。表是在内部进行分区的,试图允许指示无争用并发更新的数量。因为哈希表中的位置基本上是随意的,所以实际的并发将各不相同。理想情况下,应该选择一个尽可能多地容纳并发修改该表的线程的值。使用一个比所需要的值高很多的值可能会浪费空间和时间,而使用一个显然低很多的值可能导致线程争用。对数量级估计过高或估计过低通常都会带来非常显著的影响。当仅有一个线程将执行修改操作,而其他所有线程都只是执行读取操作时,才认为某个值是合适的。此外,重新调整此类或其他任何种类哈希表的大小都是一个相对较慢的操作,因此,在可能的时候,提供构造方法中期望表大小的估计值是一个好主意。

此类及其视图和迭代器实现了 MapIterator 接口的所有可选 方法。

此类与 Hashtable 相似,但与 HashMap 不同,它 允许将 null 用作键或值。

 

 这两个对象的加载因子都是0.75,因此可以考虑进行替换!

缓存类:

/**
 * @说明 将监控获取的网络值进行内存缓存
 * @author cuisuqiang
 * @version 1.0
 * @since
 */
public class JianKongMapKeep {	
	/**
	 * 使用线程安全的Map对象
	 */
	public static Map<String,JianKongKeep> keepMaps = new ConcurrentHashMap<String,JianKongKeep>();	
	/**
	 * 获得某值
	 * @param key
	 * @return
	 */
	public static JianKongKeep getKeepListByKey(String key){
		JianKongKeep jianKongKeep = new JianKongKeep();
		jianKongKeep = keepMaps.get(key);
		return jianKongKeep;
	}
	/**
	 * 新增或更新
	 * @param key
	 * @param jianKongKeep
	 */
	public static void saveOrUpdateJianKongKeep(String key,JianKongKeep jianKongKeep){
		keepMaps.put(key, jianKongKeep);
	}
}

 

定时器执行类:

/**
 * @说明 定时清理监控的内存内容
 * @author cuisuqiang
 * @version 1.0
 * @since
 */
public class JianKongMapKeepTimer {
	public void checkJianKongKeep(){
		try {
			Map<String,JianKongKeep> keepMaps = JianKongMapKeep.keepMaps;
			for(String key : keepMaps.keySet()){
				JianKongKeep jianKongKeep = keepMaps.get(key);
				Date date = jianKongKeep.getLastUpdate();
				Date nowDate = new Date();		
				long c = DataUtil.TimeDiff(nowDate,date);
				// 清理大于一分钟的内存内容
				if(c > 1 * 60){
					keepMaps.remove(key);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}		
	}
}

 

 

经过测试是没有问题!

如果大家有其他解决办法或者更合理更高效的缓冲解决方案,欢迎指点!

 

请您到ITEYE看我的原创:http://cuisuqiang.iteye.com

或支持我的个人博客,地址:http://www.javacui.com

 

2
2
分享到:
评论
10 楼 hxr521521 2012-07-04  
hxr521521 写道
cuisuqiang 写道
LRUMap
hxr521521 写道
LRUMap不行吗

JDK中为什么没有查到这个对象?

org.apache.commons.collections.map.LRUMap.LRUMap 找这个 网上百度LRU算法看看是不是适合你

commons-collections.jar
9 楼 hxr521521 2012-07-04  
cuisuqiang 写道
LRUMap
hxr521521 写道
LRUMap不行吗

JDK中为什么没有查到这个对象?

org.apache.commons.collections.map.LRUMap.LRUMap 找这个 网上百度LRU算法看看是不是适合你
8 楼 cuisuqiang 2012-07-04  
LRUMap
hxr521521 写道
LRUMap不行吗

JDK中为什么没有查到这个对象?
7 楼 hxr521521 2012-07-04  
LRUMap不行吗
6 楼 cuisuqiang 2012-06-29  
scriptguy 写道
如果你的这个定时器执行类只会由一个线程调用,那么完全没有必要使用ConcurrentHashMap,完全可以使用HashMap,只不过下面的代码
11.            for(String key : keepMaps.keySet()){   
12.                JianKongKeep jianKongKeep = keepMaps.get(key);   
13.                Date date = jianKongKeep.getLastUpdate();   
14.                Date nowDate = new Date();         
15.                long c = DataUtil.TimeDiff(nowDate,date);   
16.                // 清理大于一分钟的内存内容   
17.                if(c > 1 * 60){   
18.                    keepMaps.remove(key);   
19.                }   
20.            }   
需要改为使用迭代器,使用iterator的remove来删除超时缓存,不会出现异常。

非常感谢,我这个是一个线程增加,一个定时器清理的
5 楼 scriptguy 2012-06-29  
如果你的这个定时器执行类只会由一个线程调用,那么完全没有必要使用ConcurrentHashMap,完全可以使用HashMap,只不过下面的代码
11.            for(String key : keepMaps.keySet()){   
12.                JianKongKeep jianKongKeep = keepMaps.get(key);   
13.                Date date = jianKongKeep.getLastUpdate();   
14.                Date nowDate = new Date();         
15.                long c = DataUtil.TimeDiff(nowDate,date);   
16.                // 清理大于一分钟的内存内容   
17.                if(c > 1 * 60){   
18.                    keepMaps.remove(key);   
19.                }   
20.            }   
需要改为使用迭代器,使用iterator的remove来删除超时缓存,不会出现异常。
4 楼 cuisuqiang 2012-06-26  
zhukewen_java 写道
杀鸡用牛刀。ConcurrentMap线程安全,但是效率自然没有hashmap高。实际应用的时候,绝大多数时候都只是读map,很少的时候是写map. 如果只为了那么几秒钟的写,而影响了N个小时的读,得不偿失。

Map m = Collections.synchronizedMap(new HashMap(...));
3 楼 zhukewen_java 2012-06-26  
杀鸡用牛刀。ConcurrentMap线程安全,但是效率自然没有hashmap高。实际应用的时候,绝大多数时候都只是读map,很少的时候是写map. 如果只为了那么几秒钟的写,而影响了N个小时的读,得不偿失。
2 楼 cuisuqiang 2012-06-19  
Shen.Yiyang 写道
API的定义都告诉你了,ConcurrentModificationException和线程没关系,是迭代器的快速失败策略。。。 这不叫线程安全

多谢指点,谨记了!
1 楼 Shen.Yiyang 2012-06-19  
API的定义都告诉你了,ConcurrentModificationException和线程没关系,是迭代器的快速失败策略。。。 这不叫线程安全

相关推荐

    java.util.ConcurrentModificationException 解决方法

    java.util.ConcurrentModificationException 解决方法 在使用iterator.hasNext()操作迭代器的时候,如果此时迭代的对象发生改变,比如插入了新数据,或者有数据被删除。 则使用会报以下异常: Java.util....

    出现java.util.ConcurrentModificationException 问题及解决办法

    这个异常的产生是由于集合类(如HashMap)的非线程安全特性,当你在一个线程中使用迭代器遍历集合,而另一个线程在同时修改这个集合时,就会触发此异常。下面我们将深入探讨这个问题的原因、示例代码以及解决策略。 ...

    java 集合并发操作出现的异常ConcurrentModificationException

    在Java编程中,`ConcurrentModificationException`是一个常见的运行时异常,主要出现在多线程环境下对集合类(如List、Set、Map等)进行并发修改时。然而,这个异常不仅限于多线程环境,即使在单线程中,如果在遍历...

    Java语言的Util类详细介绍

    但是,因为Vector是同步的,当一个Iterator被创建而且正在被使用,另一个线程改变了Vector的状态(例如,添加或删除元素),Iterator将抛出ConcurrentModificationException异常。 Set接口是Collection接口的子接口...

    Java多线程安全集合

    // 在多线程环境中使用这些集合进行操作... } } ``` 理解并熟练运用这些线程安全集合是构建健壮、高性能的多线程Java应用程序的基础。它们能帮助开发者编写出更安全、更高效的代码,避免因并发问题导致的错误。...

    jdk 1.6 API 中文版帮助文档

    - `java.util.Set`和`java.util.Map`接口的实现:添加了`LinkedHashSet`和`LinkedHashMap`,保持插入顺序。 - `java.util.Iterator`的改进:支持`remove()`操作,避免抛出`ConcurrentModificationException`。 ### ...

    Java源码分析:深入探讨Iterator模式

    在Java编程语言中,集合框架(`java.util`包)提供了多种容器类来存储对象,如`List`、`Set`和`Map`等。为了遍历这些容器中的元素,Java引入了迭代器模式(Iterator Pattern),这是一种常用的设计模式,它提供了一...

    Java 7编程高级进阶

    `java.util.concurrent.ForkJoinPool`和`java.util.concurrent.RecursiveTask`是其核心类。 7. **非阻塞堆栈跟踪(Non-blocking Stack Traces)** 当线程处于等待状态时,Java 7可以生成不包含阻塞信息的堆栈跟踪...

    Java 实例 - 只读集合源代码+详细指导教程.zip

    在多线程环境中,只读集合特别有用,因为它们能防止并发修改异常(`ConcurrentModificationException`)。当多个线程试图同时修改集合时,可能会出现这种问题。只读集合确保了即使在并发环境下,数据也能保持一致。 ...

    使用Iterator接口遍历集合元素

    否则将会引发 java.util.ConcurrentModificationException 异常。 3. Iterator 迭代器采用的是快速失败(fail-fast)机制,一旦在迭代过程中检测到该集合已经被修改(通常是程序中其它线程修改),程序立即引发 ...

    java实现遍历Map的方法

    在Java编程中,Map接口是集合框架的一部分,它存储键值对的数据结构。遍历Map是常见的操作,以便访问或处理其中的元素。本篇文章将详细介绍如何在Java中实现遍历Map,特别是针对HashMap的遍历技巧。 首先,Java提供...

    高级程序员必会的HashMap的线程安全问题,适用于0~2年的.7z

    1. **使用线程安全的类**:Java提供了一些线程安全的Map实现,如`java.util.concurrent.ConcurrentHashMap`。ConcurrentHashMap使用分段锁技术,使得在保证线程安全的同时,提供了较好的并发性能。 2. **同步访问**...

    Java使用keySet方法获取Map集合中的元素

    此外,在使用keySet方法获取Map集合中的元素时,我们还需要注意Map集合中的元素是否可以被修改,如果Map集合中的元素可以被修改,那么使用keySet方法将会抛出ConcurrentModificationException异常。因此,在实际应用...

    13.foreach循环_java_

    这里的“元素类型”是数据源中的元素类型,“变量名”是你在循环中使用的临时变量,而“数据源”可以是数组或实现了`Iterable`接口的集合。 1. **遍历数组**: 对于数组,你可以这样做: ```java int[] numbers ...

    java-collection-all-in-one.pdf

    它支持协变、逆变等高级特性,但在使用中需要注意类型擦除带来的影响,并通过通配符、类型边界等方法解决泛型在使用中可能遇到的问题。 类型擦除(Type Erasure)是Java泛型的一种机制,它在运行时移除泛型信息,以...

    Java程序员们最常犯的10个错误.docx

    在Java编程中,程序员们常常会遇到一些常见的错误,这些错误可能在初学者和经验丰富的开发者之间都有所体现。本文将探讨Java程序员们最常犯的10个错误,并给出相应的纠正方法,这对于准备Java面试或者日常开发工作都...

    多线程程序避免冲突的3条简单规则

    4. `java.util.concurrent.atomic`包:提供了一组原子类,如`AtomicInteger`、`AtomicLong`等,它们的原子操作在多线程环境中避免了锁的开销。 遵循以上规则,可以显著提高多线程程序的并发性能和稳定性。然而,...

Global site tag (gtag.js) - Google Analytics