欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

java.util.ConcurrentModificationException

程序员文章站 2024-03-25 15:17:58
...

Where ConcurrentModificationException raised

Case 1: update value Entries during traverse Map in same Thread

java.util.ConcurrentModificationException will be raised in following java code:

 

		Map<String, String> pendingCorrelations = new Hashtable<String, String>();
		pendingCorrelations.put("a", "a1");
		pendingCorrelations.put("b", "b1");
		pendingCorrelations.put("c", "c1");
		pendingCorrelations.put("d", "d1");
		pendingCorrelations.put("e", "e1");
		Set<String> keys = pendingCorrelations.keySet();
		for (String key : keys) { // Here raise ConcurrentModificationException after a is removed.
			if (key.equalsIgnoreCase("a")) {
				 pendingCorrelations.remove(key); 
			}
		}

Exception:

 

java.util.ConcurrentModificationException
	at java.util.Hashtable$Enumerator.next(Hashtable.java:1031)

 

Case 2: This exception could happen in case many simultaneouse threads manipulate same value Object.

 

Why ConcurrentModificationException raised?

Here we focous on two points:

  1. How Java analyze for() element?
  2. What happened after Hashtable.remove(Object)?

We can image JDK will analyze for() as following:

 

	for(String key=Hashtable$Enumerator.next();Hashtable$Enumerator.hasMoreElements();){
		pendingCorrelations.remove(key);
	}

 

An interesting source code to understand well for() elements:

 

		Map<String, String> pendingCorrelationsMap = new HashMap<String, String>();
		pendingCorrelationsMap.put("c", "c1");
		pendingCorrelationsMap.put("b", "b1");
		pendingCorrelationsMap.put("d", "b1");
		pendingCorrelationsMap.put("a", "a1");

		for (String key : pendingCorrelationsMap.keySet()) {
			if (key.equalsIgnoreCase("a")) { // won't cause ConcurrentModificationException
				pendingCorrelationsMap.remove(key);
			}
		}


		for (String key : pendingCorrelationsMap.keySet()) {
			if (key.equalsIgnoreCase("c")) { // will cause ConcurrentModificationException
				pendingCorrelationsMap.remove(key);
			}
		}
 

 

java.util.Hashtable

 

    /**
     * The number of times this Hashtable has been structurally modified
     * Structural modifications are those that change the number of entries in
     * the Hashtable or otherwise modify its internal structure (e.g.,
     * rehash).  This field is used to make iterators on Collection-views of
     * the Hashtable fail-fast.  (See ConcurrentModificationException).
     */
    private transient int modCount = 0;



	/**
	 * The modCount value that the iterator believes that the backing
	 * Hashtable should have.  If this expectation is violated, the iterator
	 * has detected concurrent modification.
	 */
	protected int expectedModCount = modCount;


    /**
     * Removes the key (and its corresponding value) from this
     * hashtable. This method does nothing if the key is not in the hashtable.
     *
     * @param   key   the key that needs to be removed
     * @return  the value to which the key had been mapped in this hashtable,
     *          or <code>null</code> if the key did not have a mapping
     * @throws  NullPointerException  if the key is <code>null</code>
     */
    public synchronized V remove(Object key) {
	Entry tab[] = table;
	int hash = key.hashCode();
	int index = (hash & 0x7FFFFFFF) % tab.length;
	for (Entry<K,V> e = tab[index], prev = null ; e != null ; prev = e, e = e.next) {
	    if ((e.hash == hash) && e.key.equals(key)) {
		modCount++;  // Only modCount updated within Hashtable.remove(Object)
		if (prev != null) {
		    prev.next = e.next;
		} else {
		    tab[index] = e.next;
		}
		count--;
		V oldValue = e.value;
		e.value = null;
		return oldValue;
	    }
	}
	return null;
    }

We can see that if any Object removed by invode Hashtable.remove(Object), only "modCount++" invoked (not expectedModCount). 

 

java.util.Hashtable$Enumerator<T>

Hashtable$Enumberator<T>

 

    /**
     * A hashtable enumerator class.  This class implements both the
     * Enumeration and Iterator interfaces, but individual instances
     * can be created with the Iterator methods disabled.  This is necessary
     * to avoid unintentionally increasing the capabilities granted a user
     * by passing an Enumeration.
     */
    private class Enumerator<T> implements Enumeration<T>, Iterator<T> {
        ...
	public T next() {
	    if (modCount != expectedModCount)
		throw new ConcurrentModificationException();
	    return nextElement();
	}

	public void remove() {
	    if (!iterator)
		throw new UnsupportedOperationException();
	    if (lastReturned == null)
		throw new IllegalStateException("Hashtable Enumerator");
	    if (modCount != expectedModCount)
		throw new ConcurrentModificationException();

	    synchronized(Hashtable.this) {
		Entry[] tab = Hashtable.this.table;
		int index = (lastReturned.hash & 0x7FFFFFFF) % tab.length;

		for (Entry<K,V> e = tab[index], prev = null; e != null;
		     prev = e, e = e.next) {
		    if (e == lastReturned) {
			modCount++; //both modCount and expectedModCount will be updated simultanously
			expectedModCount++;
			if (prev == null)
			    tab[index] = e.next;
			else
			    prev.next = e.next;
			count--;
			lastReturned = null;
			return;
		    }
		}
		throw new ConcurrentModificationException();
	    }
	}
        ...
}

 

 Hashtable$Enumberator.next() will check (modCount != expectedModCount) to fast fail. Here can see how "java.util.ConcurrentModificationException" raised.

 

Solutions

Solution 1: Update(Remove) java collections through Iterator

As we list in prior java source code, we can see both "modCount" and "expectedCount" will be udpated simultanousely in Hashtable$Enumberator which will be return when calling Hashtable.interator(). 

So we can remove elements as following without ConcurrentModificationException:

 

Iterator iter = Collections.iterator();
while(iter.hasNext()) {
        iter.next(); // Iterator.next() must be called before calling remove()
        iter.remove(); // should be called once and only once
}

Note:

 

  1. Here "Collections" is only a symbol of set of objects it is not "java.util.Collection".
  2. We can only invoke remove through Iterator. (because there is no other udpate operation API.)

 Solution 2: Remove Objects from Collections outof Traverse Collections.

 

		Map<String, String> pendingCorrelations = new Hashtable<String, String>();
		pendingCorrelations.put("a", "a1");
		pendingCorrelations.put("b", "b1");
		pendingCorrelations.put("c", "c1");
		pendingCorrelations.put("d", "d1");
		pendingCorrelations.put("e", "e1");
		Set<String> keys = pendingCorrelations.keySet();
		List<String> tobeRemvoed = new ArrayList<String>();
		for (String key : keys) {
			if (key.equalsIgnoreCase("a")) {
				tobeRemvoed.add(key);
				// pendingCorrelations.remove(key);
			}
		}

		assertEquals(5, pendingCorrelations.size());
		for (String str : tobeRemvoed) {
			pendingCorrelations.remove(str);
		}
		assertEquals(4, pendingCorrelations.size());