ConcurrentHashMap Iterator
Hello,
I have the following ConcurrentHashMap that represents a cache:
privatefinal ConcurrentHashMap<String, dtoCache> cache;
And I have the following method that iterates over the CHM, removing all entries that have expired:
Iterator<Map.Entry><String, dtoCache>> itEntry = this.cache.entrySet().iterator();
while (itEntry.hasNext())
{
// "key" = ID cach? "value" = cache object.
Map.Entry<String, dtoCache> entry = itEntry.next();
dtoCache objCache = entry.getValue();
// If the entry has expired, call method "removeEntry".
if (this.isExpired(objCache.fechaIni, cachearDurante, lNow))
{
String idCache = entry.getKey();
this.removeEntry(idCache, objCache);
}
}
But I have another method that can remove entries in this CHM. Then the question is: what happens if, at the same time, one thread is executing
Map.Entry<String, dtoCache> entry = itEntry.next();
dtoCache objCache = entry.getValue();
while the other thread is removing this same entry?
Should I lock both methods?
try
{
// Acquired the lock.
this.removeLock.lock();
Iterator<Map.Entry><String, dtoCache>> itEntry = this.cache.entrySet().iterator();
while (itEntry.hasNext())
{
// the same while ......
}
}
finally
{
// Release the lock
this.removeLock.unlock();
}
# 5
Hello ejp,
In my app, there are 2 ways to remove elements from my CHM:
1. With a set of schedulers that iterate over the CHM removing the expired elements.
2. Expiration by rules when a request arrives. For example,I can have a rule that says: "If the city is NY, remove from cache all elements that the airport is "Kennedy" OR "La Guardia".
These processes are concurrent: I can start the scheduler at the same time I have 4 rules removing elements from the CHM (these rules don't iterate, just execute CHM.remove(idCache))
That's why I need to avoid locking the cache when the scheduler is iterating, otherwise I'll lock all my rules.
And that's why I'm using CHM, because I though that it was possible to remove elements from the CHM while another thread is iterating over it, due to the "weakly consistent" iterator.
You see any way to avoid this locking? Is it possible to make something like this?
Iterator<Map.Entry><String, dtoCache>> itEntry = this.cache.entrySet().iterator();
while (itEntry.hasNext())
{
// "key" = ID cach? "value" = cache object.
Map.Entry<String, dtoCache> entry = itEntry.next();
dtoCache objCache = null;
try { objCache = entry.getValue().clone(); }
catch (Exception e)
{
// CloneNotSupportedException or InvalidStateException
objCache = null;
}
if (objCache != null)
{
// Here "objCache" exists and is a copy, then I don't worry if another
// thread removes this entry from CHM
do something with "objCache"
}
}
Thanks in advance.
# 7
Thanks for your reply, ejp
Here I pass a test case with a ConcurrentHashMap. I think that it can help to people who are working with iterators and ConcurrentHashMap. It's a bit large but very very simple.
package com.vcfw.admin.utils;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class testConcurrent
{
public static void main(String[] args) throws Exception
{
final ConcurrentHashMap<String,String> chMap = new ConcurrentHashMap<String,String>();
final int LIMIT = 20;
class modifyCHM extends Thread
{
String operation;
int sleep;
public modifyCHM(String operation, int sleep)
{
this.operation = operation;
this.sleep = sleep;
}
// Run
public void run()
{
try
{
Thread.sleep(this.sleep);
if (this.operation.equals("A"))
{
System.out.println("Start 'A'");
// If (A)dd, we add odd values between (0, 20)
for (int i = 1; i < LIMIT; i+=2)
chMap.put(String.valueOf(i), String.valueOf(i));
}
else if (this.operation.equals("R"))
{
System.out.println("Start 'R'");
// If (R)emove, we remove multiples of 4 between (0, 20)
for (int i = 0; i <= LIMIT; i+=4)
chMap.remove(String.valueOf(i));
}
else if (this.operation.equals("C"))
{
// Clear chMap.
System.out.println("Start 'C'");
chMap.clear();
}
// Show chMap after operation.
Iterator<Map.Entry><String,String>> it = chMap.entrySet().iterator();
while (it.hasNext())
{
Map.Entry<String,String> entry = it.next();
System.out.print(entry.getKey() + ", ");
}
System.out.println("");
if (this.operation.equals("A")) System.out.println("Finish 'A'");
else if (this.operation.equals("R")) System.out.println("Finish 'R'");
else if (this.operation.equals("C")) System.out.println("Finish 'C'");
}
catch (Exception e) {}
}
}
// Main Program. Insert even values between [0,20]
for (int i = 0; i <= LIMIT; i+=2)
{
chMap.put(String.valueOf(i), String.valueOf(i));
}
// Here, chMap = { 0,2,4,6,8,10,12,14,16,18,20 }
// Start 3 Threads that wait for 2,4 and 6 seconds.
new modifyCHM("A", 2000).start(); // Add odd elements between [0,20].
new modifyCHM("R", 4000).start(); // Remove multiples of 4 between [0,20].
new modifyCHM("C", 6000).start(); // Clear CHM.
// Now start to iterate...
Iterator<Map.Entry><String,String>> it = chMap.entrySet().iterator();
// Sleep until all others threads finish.
Thread.sleep(8000);
System.out.println("Main Thread");
// Print the Concurrent...
while (it.hasNext())
{
Map.Entry<String,String> entry = it.next();
System.out.print(entry.getKey() + ", ");
}
System.out.println("\nEnd Main Thread");
}
}
And this is the result of the execution:
Start 'A'
8, 9, 4, 5, 6, 7, 0, 1, 10, 20, 2, 3, 12, 11, 14, 13, 16, 15, 18, 17, 19,
Finish 'A'
Start 'R'
9, 5, 6, 7, 1, 10, 2, 3, 11, 14, 13, 15, 18, 17, 19,
Finish 'R'
Start 'C'
Finish 'C'
Main Thread
12, 14, 16, 8, 18, 4, 6, 0, 1, 10, 20, 2, 3,
End Main Thread
When the main program starts, it inserts the even values between 0 and 20 in the CHM. Then, it starts 3 threads every 2 seconds that:
* thread A: inserts odd values between 0 and 20.
* thread R: removes values multiples of 4.
* thread C: clear the CHM.
After this, the main program creates an iterator BEFORE THE 3 THREADS ARE GONNA TO BE EXECUTED, and pauses for 8 seconds.
As we can see, after execution is completed, the main iterator is trying to show the values that were in the CHM at creation time of it with some modifications (appear numbers 1 and 3 from the thread A).
Now if you move the "Thread.sleep(8000);" before the creation of the iterator, when it's executed the result is an empty CHM because the creation time is after executing thread C.
My conclusions are:
1. It's not necessary to lock a CHM while iterating (even it there are others threads trying to add/remove entries).
2. The iterator will always try to show the values that existed in the CHM at creation time of the iterator, and there is no guarantee to reflect any external change.
3. And this is fantastic because it's just I need.
Now, taking a look to the documentation, this is just it says:
1. "Similarly, Iterators and Enumerations return elements reflecting the state of the hash table at some point at or since the creation of the iterator/enumeration. They do not throw ConcurrentModificationException."
2. EntrySet, "The view's iterator is a "weakly consistent" iterator that will never throw ConcurrentModificationException, and guarantees to traverse elements as they existed upon construction of the iterator, and may (but is not guaranteed to) reflect any modifications subsequent to construction."
Thanks for all.