项目中的java.util.ConcurrentModificationException异常

异常的相关文档在网上比比皆是,这里不具体描述java类的内部处理方式,仅仅就访问页面标签时发生该异常讨论起。只阐述可能会引起该异常的地方而不具体阐明为什么会出现。
项目相关环境:linux、jdk5.0、oracle10g、spring2.0+hibernate3.2+webwork2.2.5
最近在服务器上运行项目访问jsp页面过程中总会抛出以下异常信息

2009-10-23 17:06:36,627 ERROR (com.opensymphony.webwork.components.UIBean:598) - an exception occurred while merging themplate [/template/simple/doubleselect]
java.util.ConcurrentModificationException<br />         at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:449)
        at java.util.AbstractList$Itr.next(AbstractList.java:420)
        at com.opensymphony.webwork.components.IteratorComponent.end(IteratorComponent.java:228)
        at com.opensymphony.webwork.views.freemarker.tags.CallbackWriter.afterBody(CallbackWriter.java:65)
        at freemarker.core.Environment.visit(Environment.java:235)
        at freemarker.core.UnifiedCall.accept(UnifiedCall.java:116)
        at freemarker.core.Environment.visit(Environment.java:196)
        at freemarker.core.MixedContent.accept(MixedContent.java:92)
        at freemarker.core.Environment.visit(Environment.java:196)
        at freemarker.core.Environment.process(Environment.java:176)
        at freemarker.template.Template.process(Template.java:232)
        at com.opensymphony.webwork.components.template.FreemarkerTemplateEngine.renderTemplate(FreemarkerTemplateEngine.java:126)
        at com.opensymphony.webwork.components.UIBean.mergeTemplate(UIBean.java:642)
        at com.opensymphony.webwork.components.UIBean.end(UIBean.java:596)
        at com.opensymphony.webwork.views.jsp.ComponentTagSupport.doEndTag(ComponentTagSupport.java:21)
        at org.apache.jsp.WEB_002dINF.pages.csrMaster.faceworkerEdit_jsp._jspx_meth_ww_005fdoubleselect_005f4(faceworkerEdit_jsp.java:1720)
        at org.apache.jsp.WEB_002dINF.pages.csrMaster.faceworkerEdit_jsp._jspx_meth_ww_005fform_005f0(faceworkerEdit_jsp.java:606)
        at org.apache.jsp.WEB_002dINF.pages.csrMaster.faceworkerEdit_jsp._jspService(faceworkerEdit_jsp.java:198)
        at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:98)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
        at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:331)
        at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:329)
        at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:265)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
但是在本地进行测试过程中却无法还原该异常。最后经过一番周折后问题最终得到解决。
首先定位问题所在,发现异常的抛出点是在webwork解析ww:doubleselect 标签时产生。该处标签源码如下
<ww:doubleselect name="domicile" id="domicileId"
						list="@com.test.ProvinceCityDoubleSelectUtil@getProvinceList()"
						doubleName="domicileCity" doubleId="domicileCityId"
						doubleList="@com.test.ProvinceCityDoubleSelectUtil@getCityMap().get(top)" />
 这里我们使用了页面调用静态方法来实现需要迭代的省市联动菜单数据。具体的类代码如下
public class ProvinceCityDoubleSelectUtils {
	private static final List<String> PROVINCELIST = new ArrayList<String>();
	private static final Map<String, List<String>> CITYMAP = new HashMap<String, List<String>>();
	private static final String[] herprovince = new String[] {  "安徽",
			"北京", "福建", "甘肃", "广东", "广西"};
	private static final String[][] hercity = new String[][] {
			{ "未知", "安庆", "蚌埠", "巢湖", "池州", "滁州", "阜阳", "合肥", "淮北", "淮南", "黄山",
					"六安", "马鞍山", "宿州", "铜陵", "芜湖", "宣城", "亳州" },
			{ "北京" },
			{ "未知", "福州", "龙岩", "南平", "宁德", "莆田", "泉州", "三明", "厦门", "漳州" },
			{ "未知", "白银", "定西地区", "甘南藏族自治州", "嘉峪关", "金昌", "酒泉地区", "兰州",
					"临夏回族自治州", "陇南地区", "平凉地区", "庆阳地区", "天水", "武威", "张掖地区" },
			{ "未知", "潮州", "从化", "东莞", "番禺", "佛山", "高明", "广州", "河源", "花都", "惠州",
					"江门", "揭阳", "茂名", "梅州", "南海", "清远", "三水", "汕头", "汕尾", "韶关",
					"深圳", "顺德", "阳江", "云浮", "增城", "湛江", "肇庆", "中山", "珠海" },
			{ "未知", "百色地区", "北海", "崇左", "防城港", "桂林", "贵港", "河池地区", "贺州地区",
					"来宾", "柳州", "南宁", "钦州", "梧州", "玉林" }
		};
	/**
	 * 得到省份列表
	 * 
	 * @return
	 */
	public static final List<String> getProvinceList() {
		  PROVINCELIST.clear();
			for (String ps : herprovince) {
				PROVINCELIST.add(ps);
			}
		return PROVINCELIST;
	}
	/**
	 * 获取跟省份有关的市信息
	 * 
	 * @return
	 */
	public static final Map<String, List<String>> getCityMap() {
		  CITYMAP.clear();
			for (int i = 0; i < herprovince.length; i++) {
				List<String> tmp = new ArrayList<String>();
				for (int j = 0; j < hercity[i].length; j++) {
					tmp.add(hercity[i][j]);
				}
				CITYMAP.put(herprovince[i], tmp);
			}
		return CITYMAP;
	}
}
 但在查看了该类的具体方法外,发现该类方法除了可以优化外,并没有能产生异常的地方。
开始怀疑是否是在调用 Collection的clear()方法是出现了该异常!但在测试过程中发现并未发现调用clear()方法会产生java.util.ConcurrentModificationException异常,同时结合API也并未发现API中有提到调用Collection 的clear()方法是会抛出该异常。
实在无奈下只好去看API中关于java.util.ConcurrentModificationException的解释,发现有如下解释

 

当方法检测到对象的并发修改,但不允许这种修改时,抛出此异常。
例如,某个线程在 Collection 上进行迭代时,通常不允许另一个线性修改该 Collection。通常在这些情况下,迭代的结果是不明确的。如果检测到这种行为,一些迭代器实现(包括 JRE 提供的所有通用 collection 实现)可能选择抛出此异常。执行该操作的迭代器称为快速失败 迭代器,因为迭代器很快就完全失败,而不会冒着在将来某个时间任意发生不确定行为的风险。
注意,此异常不会始终指出对象已经由不同 线程并发修改。如果单线程发出违反对象协定的方法调用序列,则该对象可能抛出此异常。例如,如果线程使用快速失败迭代器在 collection 上迭代时直接修改该 collection,则迭代器将抛出此异常。
注意,迭代器的快速失败行为无法得到保证,因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证。快速失败操作会尽最大努力抛出 ConcurrentModificationException。因此,为提高此类操作的正确性而编写一个依赖于此异常的程序是错误的做法,正确做法是:ConcurrentModificationException 应该仅用于检测 bug


那么该异常既然是由于是在多线程下才会发生那么首先根据API的提示写个测试类先还原该问题,写测试类如下

public static void main(String[] args) {
		final List<String> aa = ProvinceCityDoubleSelectUtil.getProvinceList();
		for (int i = 0; i < 10; i++) {
			Thread thread = new Thread() {
				@SuppressWarnings("static-access")
				@Override
				public void run() {
					try {
						for (String s : aa) {
							System.out.print(this.getName() + ">>>" + s + "/n");
							this.sleep(5000);//休息5ms
						}
					} catch (InterruptedException e) {
						e.printStackTrace();
					} catch (java.util.ConcurrentModificationException e) {
						System.out.println(this.getName() + ">>>erro");
					}
					super.run();
				}
			};
			thread.start();
		}
		final List<String> bb = ProvinceCityDoubleSelectUtil.getProvinceList();
	}
果不其然,在这种情况下打印出“ error ”。将每个线程在取一个值时的休息时间改大时,程序抛出error的个数也个增加。那么现在就可以解释,为什么同一段程序在测试过程不会发生该异常,是由于页面的并发访问量没有达到让不同线程对省市联动集合进行迭代和clear()冲突的程度。
就像API中对于java.util.ConcurrentModificationException的解释,在对Collection迭代的过程中,是不允许其他线程对该共享Collection进行值修改的,如果修改就会抛出java.util.ConcurrentModificationException;同时经过测试发现,该异常通常不会出现在对Collection进行clear()、remove()时,而是出现在对Collection进行迭代的过程中.
更改静态类方法即可避免该问题
/**
	 * 得到省份列表
	 * 
	 * @return
	 */
	public static final List<String> getProvinceList() {
		if (PROVINCELIST.isEmpty()) {// 判断静态常量是否为空,为空时填充数据;不为空表明数据已填充过
			for (String ps : herprovince) {
				PROVINCELIST.add(ps);
			}
		}
		return PROVINCELIST;
	}
	/**
	 * 获取跟省份有关的市信息
	 * 
	 * @return
	 */
	public static final Map<String, List<String>> getCityMap() {
		if (CITYMAP.isEmpty()) {// 判断静态常量是否为空,为空时填充数据;不为空表明数据已填充过
			for (int i = 0; i < herprovince.length; i++) {
				List<String> tmp = new ArrayList<String>();
				for (int j = 0; j < hercity[i].length; j++) {
					tmp.add(hercity[i][j]);
				}
				CITYMAP.put(herprovince[i], tmp);
			}
		}
		return CITYMAP;
	}

转载于:https://my.oschina.net/yeelee/blog/650885

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值