What's the main story of this blog?
- Partial source code Analysis of Hashtable and its inner class
- Hashtable and resolution of java.util.ConcurrentModificationException anomalies during traversal
- A single machine caches data in memory and periodically clears the simple implementation of expired caches
The cause of the matter
At work, you need to set up a caching mechanism (standalone) that caches some objects in memory in a business class. Thus, the following similar structures are implemented:
1 Packageorg.cnblog.test;2 3 Importjava.util.Hashtable;4 ImportJava.util.Iterator;5 6 /**7 * Java Hashtable iterator threading problem at traverse time8 * @authorHY9 */Ten Public classHashtableiteratortest { One A //initializes the cache and initiates an event that refreshes the cache. - Static { -Cache.cachemap =NewHashtable<string, long>(); the NewCache (). Start (); - } - - /** + * Execute Main method - * @paramargs + */ A Public Static voidMain (string[] args) { at -Thread T =NewThread (NewRunnable () { - Public voidrun () { - while(true) { - LongTime =System.currenttimemillis (); -Cache.cacheMap.put (Time + "", time); inSystem.out.println ("[" + Thread.CurrentThread (). GetName () + "]cache New cache >>" +Time ); - Try { to //Add one cache instance per second. +Thread.Sleep (1*1000); -}Catch(interruptedexception e) { the e.printstacktrace (); * } $ }Panax Notoginseng } - }); the T.start (); + } A the Private Static classCacheextendsThread { + Private StaticHashtable<string, long>Cachemap; - $ /** $ * Refresh cached methods to clear cache for longer than 10 seconds. - */ - Private voidRefresh () { the synchronized(cachemap) { - String key;Wuyiiterator<string> i =Cachemap.keyset (). iterator (); the while(I.hasnext ()) { -Key =I.next (); Wu if(Cachemap.get (key)! =NULL&& System.currenttimemillis ()-Cachemap.get (key) > 10*1000) { - Cachemap.remove (key); AboutSystem.out.println ("[" + Thread.CurrentThread (). GetName () + "] deleted key value <<" +key); $ } - } - } - } A + Public voidrun () { the while(true) { - refresh (); $ Try { the //cache refresh every 10 seconds theThread.Sleep (10*1000); the}Catch(interruptedexception e) { the e.printstacktrace (); - } in } the } the } About}
In the business class Hashtableiteratortest, a static internal class cache is used to store the cache, and the direct vector of the cache is the static member Cachemap in the inner class.
The internal class cache is the thread class, and the thread executes a cache refresh every 10 seconds. (The refresh result is to clear the cache for more than 10 seconds)
The business class Hashtableiteratortest starts the thread of the inner class at initialization, and implements some methods for caching and reading the cache.
The main method in the code simulates adding a cache per second.
The code then encounters the following problem:
[Thread-1] New Cache >>1418207644572 in caches
[Thread-1] New Cache >>1418207645586 in caches
[Thread-1] New Cache >>1418207646601 in caches
[Thread-1] New Cache >>1418207647616 in caches
[Thread-1] New Cache >>1418207648631 in caches
[Thread-1] New Cache >>1418207649646 in caches
[Thread-1] New Cache >>1418207650661 in caches
[Thread-1] New Cache >>1418207651676 in caches
[Thread-1] New Cache >>1418207652690 in caches
[Thread-1] New Cache >>1418207653705 in caches
[Thread-0] deleted key value <<1418207644572
Exception in Thread "Thread-0" java.util.ConcurrentModificationException
At Java.util.hashtable$enumerator.next (Unknown Source)
At Org.cnblog.test.hashtableiteratortest$cache.refresh (hashtableiteratortest.java:53)
At Org.cnblog.test.hashtableiteratortest$cache.run (hashtableiteratortest.java:64)
In line 53 above, the java.util.ConcurrentModificationException exception is thrown when iterating the cache map.
Resolution process
First, Concurrentmodificationexception is described in the JDK as:
This exception is thrown when the method detects concurrent modifications to an object, but does not allow this modification.
Strangely, I was locked in the Cachemap object when I was walking through the Cachemap in Refresh (), but this exception was thrown at next.
Then look at the JDK source code, found:
At Cachemap.keyset ()
Public Set<k> KeySet () { ifnull) = Collections.synchronizedset (new this); return KeySet;}
Keyset is a subclass of the set interface and is the inner class of the Hashtable. Returns the object that keyset the wrapped class Synchronizedset after it has been locked.
Part of the source code for the Synchronizedset class is as follows:
Public<T>t[] ToArray (t[] a) {synchronized(mutex) {returnC.toarray (a);}} PublicIterator<e>iterator () {returnC.iterator ();//must is manually synched by user!} Public BooleanAdd (e e) {synchronized(mutex) {returnC.add (e);}} Public BooleanRemove (Object o) {synchronized(mutex) {returnC.remove (o);}}
The variable C in the code is the keyset object, and the mutex is the object that calls the keyset () method, that is, the lock object is Cachemap. (the principle of collections synchronous set )
Note the comments in the iterator () method in the code: the user must synchronize manually!
So the author seems to have found some clues.
When you get an iterator, Cachemap.keyset (). Iterator ():
The keyset iterator () method eventually returns an enumerator object, and enumerator is the inner class of Hashtable. The following intercept important code:
1 PublicT Next () {2 if(Modcount! =expectedmodcount)3 Throw Newconcurrentmodificationexception ();4 returnnextelement ();5 }6 7 Public voidRemove () {8 if(!iterator)9 Throw Newunsupportedoperationexception ();Ten if(lastreturned = =NULL) One Throw NewIllegalStateException ("Hashtable Enumerator"); A if(Modcount! =expectedmodcount) - Throw Newconcurrentmodificationexception (); - the synchronized(Hashtable. This) { -entry[] tab = Hashtable. This. Table; - intIndex = (lastreturned.hash & 0x7FFFFFFF)%tab.length; - + for(entry<k,v> e = Tab[index], prev =NULL; E! =NULL; -Prev = e, E =e.next) { + if(E = =lastreturned) { Amodcount++; atexpectedmodcount++; - if(prev = =NULL) -Tab[index] =E.next; - Else -Prev.next =E.next; -count--; inlastreturned =NULL; - return; to } + } - Throw Newconcurrentmodificationexception (); the } *}
As you can see, the source of the problem has been found, and when modcount! = Expectedmodcount, an exception is thrown.
So, what do modcount and Expectedmodcount do?
Modcount and expectedmodcount are type int
Modcount field in its outer class hashtable, the approximate meaning of the note is: This number records the number of operations that have changed the internal structure of the Hashtable. In the case of rehash (), Put (K key, V value), there will be modcount++.
The Expectedmodcount field is in the enumerator class and is given the value of Modcount when the enumerator (iterator) is initialized. The main content of its annotations is: used to detect concurrent modifications.
Its value is increased with Modcount in the Remove () method of the iterator (see lines 22, 23 in the Remove () method in the preceding code).
The truth floats on the surface: When an iterator is obtained, the expectedmodcount is equal to the Modcount value, but at the same time as the iteration, the Cachemap.remove (key) of line 55th causes the Modcount value to increment by 1, resulting in modcount! = Expectedmodcount, then throws Concurrentmodificationexception exception.
Results
It is concluded from the above conclusion that:
In the process of Hashtable iteration, in addition to the operation in the iterator, when the map object has a structural change in the operation, is a concurrent modification. Iterators will not work correctly.
This is the reason that this kind of Hashtable throws the concurrentmodificationexception exception when traversing, and it is not the problem to synchronize two operations with the lock.
The workaround for this article is simple: Delete an object using a map Call of 55 rows
- Cachemap.remove (key);
To delete an object in an iterator instead
- I.remove ();
Can.
This is also inferred to resolve this type of exception:
Either do not rehash (), Put (K key, V value), remove (Object key), and so on in the iteration, which will change the map structure, or do the possible operation in the iterator.
The iterator thread problem for Java Hashtable at traverse time