ConcurrentModificationException
问题
项目中有一处多线程访问HashTable的地方;一个线程对 HashTable进行遍历,一个线程对它进行增、删操作。项目运行一段时间之后,就时不时的报一个java.util.ConcurrentModificationException 出来,然后线程死翘翘了。
分析
上网查了一下,这个异常的原因是在对HashTable进行遍历时,对HashTable进行了增、删操作。如果是单线程的操作,应该 把增、删操作拿到HastTable外面来;如果是多线程的操作,似乎就要使用多线程同步机制了。
但是,并没有这么复杂。我写了一个测试类,分别测试了两种遍历和增删HashTable的方法。代码如下:
package test;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import org.junit.Before;
import org.junit.Test;
public class HasttableTest {
private Hashtable<String, String> table;
private int i;
@Before
public void setUp() throws Exception {
i = 0;
table = new Hashtable<String, String>();
table.put("a", "aa");
table.put("b", "bb");
table.put("c", "cc");
}
@Test
public void testEnu() {
System.out.println("Enumeration 遍历开始");
Enumeration<String> keys = table.keys();
while (keys.hasMoreElements()) {
i++;
String key = (String) keys.nextElement();
System.out.println(key + "---" + table.get(key));
System.out.println("i=" + i);
if (i == 1) {
table.put("d", "dd");
table.remove("b");
}
}
System.out.println("Enumeration 遍历结束");
}
@Test
public void TestIte() {
System.out.println("Iterator遍 历开始");
Iterator<String> it = table.keySet().iterator();
while (it.hasNext()) {
i++;
String key = (String) it.next();
System.out.println(key + "---" + table.get(key));
System.out.println("i=" + i);
if (i == 1) {
// table.put("d", "dd");
table.remove("c");
}
}
System.out.println("Iterator 遍历结束");
}
}
执行的结果是,testEnu()方法能够正常执行完毕,而TestIte()方法则会跑出异常。
为什么会这样?我上网查了一下,准确的答案忘记了,大体上是说,HashTable、Enumeration等是线程安全的,在多线程操作中会自己 进行同步;而Iterator等则是不安全的,如果使用它,就要由程序显式的进行同步操作。
补充
若干年以后,我又犯了这个错误。这次出错的地方比较“隐晦”,可以看看下面的代码:
public class SomeThread extends Thread{
private static final Logger LOG = LogerFactory.getLogger(SomeThread.class);
private Map<String, String> map = new HashMap<>();
private final String key;
public SomeThread(String k){
this.key = key;
}
public void run(){
String value = ...// 用某种方式生成value。这段保证线程安全
map.put(key, value);// 保证不同线程操作不同的key
log.info("线程操作结果: key = {}, map = {}", key, map); // 这一行抛出了一场
}
}