欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-05-23 13:37:18
...

我想对数据访问做一个缓冲,选用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