美文网首页
concurrentHashMap对concurrentModi

concurrentHashMap对concurrentModi

作者: Shokka | 来源:发表于2018-11-18 03:37 被阅读0次
为什么抛异常?

在非同步容器中,例如ArrayList与HashMap,其使用场景是在单线程环境中,抛concurrentModificationException是为了防止在多线程场景下容器使用出现错误。例如第一个线程读,第二个线程删除了一个元素,导致第一个线程抛出了数组越界异常。这只是个例子,对于多线程的不可见性还会出现hashmap扩容死循环等问题,所以抛concurrentModificationException是为了避免更多的错误发生。

为什么HashTable也会抛此异常?

HashTable是一个同步容器,但是因为HashTable生成Iterator迭代器时,是new一个迭代器对象。

private <T> Iterator<T> getIterator(int type) {
        if (count == 0) {
            return Collections.emptyIterator();
        } else {
            return new Enumerator<>(type, true);
        }
    }

所以多线程环境下,其他线程并不能修改本线程迭代器的exceptModCount,所以当next或hasNext时依然会报异常。
疑问:作为同步容器为什么还需要报此异常?
想必都知道HashTable的get,put,remove操作都是对整个表上锁的,也就是remove方法是synchronized方法,由此做到同步。但是!Iterator的方法并没有做到同步,甚至同一个线程获取两次iterator是两个iterator对象,也就是说当其他线程在remove的时候,该线程依然可能通过iterator对象的next方法抛出数组越界等异常。

private class Enumerator<T> implements Enumeration<T>, Iterator<T> {
        Entry<?,?>[] table = Hashtable.this.table;
        protected int expectedModCount = modCount;
        int index = table.length;
        Entry<?,?> entry;
        Entry<?,?> lastReturned;
        int type;
        public T next() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            return nextElement();
        }
        public T nextElement() {
            Entry<?,?> et = entry;
            int i = index;
            Entry<?,?>[] t = table;
            /* Use locals for faster loop iteration */
            while (et == null && i > 0) {
                et = t[--i];
            }
            entry = et;
            index = i;
            if (et != null) {
                Entry<?,?> e = lastReturned = entry;
                entry = e.next;
                return type == KEYS ? (T)e.key : (type == VALUES ? (T)e.value : (T)e);
            }
            throw new NoSuchElementException("Hashtable Enumerator");
        }
为什么concurrentHashMap不会抛此异常?

1.因为concurrentHashMap读写不互斥,所以当其他线程正在修改容器中部分副本时,读操作不受影响。
2.table用violatile修饰,使得读操作可以收到更新。加上hapend-before机制,避免读取到更新前的数据。
注意:violatile修饰数组时,修饰的是数组的地址而不是数组的元素。
既然volatile修饰数组对get操作没有效果那加在数组上的volatile的目的是什么呢?
其实就是为了使得Node数组在扩容的时候对其他线程具有可见性而加的volatile

https://www.jianshu.com/p/5bc70d9e5410

3.由于读写机制都是通过violatile实现的,所以在迭代的时候保证了数据的可见性,因此不会出现数组越界等异常,所以不需要抛concurrentModificationException

相关文章

网友评论

      本文标题:concurrentHashMap对concurrentModi

      本文链接:https://www.haomeiwen.com/subject/ycewfqtx.html