“Java中到底是Map依赖Set还是Set依赖Map?”
初学Java时曾经被老师考过的一个基础问题,在自己的数学印象里,集合的结构要比映射的结构简单;但实际上,Java的Set确实依赖Map实现的,在HashSet的源码中,存在着一个Map成员。这样一个基础的问题成了本次错误的根源。
本次的情况及其简单,在并发的情况下,存在着一个共享的Hashtable
一个线程使用keySet方法得到其key集合,并对该集合进行遍历。
另一个线程对Hashtable进行put操作,此时出现ConcurrentModificationException
public static void main(String[] args) {
final Map<Integer,String> te = new Hashtable<>();
te.put(1,"a");
te.put(15,"a");
final Set<Integer> keys = te.keySet();
Runnable r1 = new Runnable(){
@Override
public void run(){
try {
Thread.sleep(3000);
te.put(6,"a");
System.out.println("insert!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Runnable r2 = new Runnable(){
@Override
public void run(){
try {
for(Integer s:keys){
System.out.println(s);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}
Hashtable是线程安全的,产生这个结果说明得到的Set集合与原有Map存在某种耦合,翻阅代码查到了这里,也恍然回想起文章开头提到的那个问题。
出现这个错误属于大意被经验所欺骗,如果基础知识够夯实也不会写出这样憋足的代码。
那么,除去Hashtable,HashMap与TreeMap的keySet方法也是遵从了相同的原则,它们均使用内部Set类型,直接或间接地使用自身Map实例进行Set构造,这也就意味着对此类Set集合的操作要加倍小心。