为什么抛异常?
在非同步容器中,例如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
3.由于读写机制都是通过violatile实现的,所以在迭代的时候保证了数据的可见性,因此不会出现数组越界等异常,所以不需要抛concurrentModificationException
网友评论