日期:2014-05-20  浏览次数:20841 次

Hashtable 多线程遍历删除的奇怪问题!!
项目中需要将一些对象存放到Hashtable中,当满足一定条件,再移除。同时还会有一个定时器定时检查Hashtable中的值,如果长时间无操作,也会移除Hashtable。
下面是代码模拟:
Java code

public class TT {
    
    public static void main(String[] args) {

        //模拟3200次
        for(int y=0;y<3200;y++){
            
            Hashtable<Integer, String> hts = new Hashtable<Integer, String>();
            for(int i=0;i<300;i++){
                hts.put(i, "value="+i);
            }
            new Thread(new TestThread2(hts)).start();
            new Thread(new TestThread(hts)).start();
        }
    }
}


#TestThread
Java code

public class TestThread implements Runnable {

    private Hashtable<Integer, String> hts;

    public TestThread(Hashtable<Integer, String> hts) {
        this.hts = hts;
    }

    @Override
    public void run() {
        //移除
        hts.remove(1);
    }

}


#TestThread2

Java code

public class TestThread2 implements Runnable {

    private Hashtable<Integer, String> hts;

    public TestThread2(Hashtable<Integer, String> hts) {
        this.hts = hts;
    }

    @Override
    public void run() {

        // 遍历
        
        
        //方法1
        Iterator<Integer> it = hts.keySet().iterator();
        while(it.hasNext()){
            int k= it.next();    //此处抛 ConcurrentModificationException
            System.out.println(k);
        }
        
        //方法2
//        Enumeration<Integer> e = hts.keys();
//        while (e.hasMoreElements()) {
//            int key = e.nextElement();    //多次测试此处不会抛异常
//            System.out.println(key+"--"+hts.get(key));
//        }
    }
}



1、遍历的时候使用方法2为何不会抛异常?查看JDK文档说Iterator接口不是替换Enumeration的吗?
2、Hashtable不是线程安全的吗,为何方法1还会有ConcurrentModificationException异常??

还请各位高手解答……

------解决方案--------------------
ConcurrentModificationException是因为你用迭代器的同时 还在外部用了remove 这是不允许的 你的remove操作也要在迭代器里完成
尝试用一下CopyOnWriteArrayList 不过它的Iterator迭代出的值只是Iterator创建的 和后来修改无关.........
------解决方案--------------------
Iterator的next方法里面会调用 checkForComodification()判断集合是否被修改过,如果修改过就抛出ConcurrentModificationException异常。所以你的程序虽然是在其他地方remove,却是在next处抛异常。这个和是否是线程安全的没关系,是迭代器不允许。

Enumeration的nextElement方法没有进行“集合是否被修改”的检测,所以没有抛出异常。


------解决方案--------------------
Hashtable 中的迭代器不是线程安全的

建议使用 ConcurrentHashMap 这个是真正意义上的线程安全,包括迭代器

另外,如果你想实现 LRU 算法的 Map 的话,推荐使用继承 LinkedHashMap 重写 removeEldestEntry 方法就可以了。