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();

}

[2168 byte] By [jbalagueroa] at [2007-11-27 5:31:44]
# 1

> 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

>

One of two things will happen. The item will be in the cache or the item will not be in the cache.

Case 1:

Item added then removed.

Case 2:

Item removed then added.

If at any of these two cases are okay then you don't need to do any locking. In either case the internally data structures of CHM will be okay. It depends if its okay for you cache to be in either state.

Ideally for a cache you would like Case 2 to happen each time. But is worth locking to insure this. I don't think so. How often will this happen? Since it is mostly likely an aged item, it isn't likely to happen very often. It also depends how expensive getting the item you are caching verses always locking for this potentially rare scenario.

When this does happen and the result is Case 1 then the item won't be in the cache, but it was likely to go away anyways and the next time it will be cached anyways.

Caffeine0001a at 2007-7-12 14:57:15 > top of Java-index,Core,Core APIs...
# 2
When I first read you message I missed what you said about both threads would be removing the same item. The CHM won't get corrupted, just make sure your code can handle it.
Caffeine0001a at 2007-7-12 14:57:15 > top of Java-index,Core,Core APIs...
# 3

Thanks Caffeine for your response, but I'm still have some doubts. Please, help me to clarify them:

1. A doubt about the iterator: if my CHM has 5 elements, my iterator is now on the element 2 and another thread remove element 3, then the next element for my iterator will be the number 4? It means, the removal of element 3 is reflected in the iterator?

2. Supposing that external removals are reflected in the iterator, then if we take a look to the following sentences in my code:

Map.Entry<String, dtoCache> entry = itEntry.next();

dtoCache objCache = entry.getValue();

The Map.Entry is a reference to a one CHM entry, or we get a copy of this entry? Because if Map.Entry is a reference, and after executing "itEntry.next();" but before executing "entry.getValue()" another thread remove this entry, then what is the state of "entry"?

Obviously I don't want to lock my method in any way. If I'm using CHM is to get more concurrency level, but I need my code to be thread safe.

I wait your response. Thanks in advance.

jbalagueroa at 2007-7-12 14:57:15 > top of Java-index,Core,Core APIs...
# 4
(a) when iterating you must remove elements only via the iterator.(b) when iterating you must hold a lock on the entire collection.(c) when adding or removing you must syncrhonize on the collection.
ejpa at 2007-7-12 14:57:15 > top of Java-index,Core,Core APIs...
# 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.

jbalagueroa at 2007-7-12 14:57:15 > top of Java-index,Core,Core APIs...
# 6
In that case (a) is the only rule above you need to observe.
ejpa at 2007-7-12 14:57:15 > top of Java-index,Core,Core APIs...
# 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.

jbalagueroa at 2007-7-12 14:57:15 > top of Java-index,Core,Core APIs...
# 8

> (a) when iterating you must remove elements only via

> the iterator.

>

> (b) when iterating you must hold a lock on the entire

> collection.

>

> (c) when adding or removing you must syncrhonize on

> the collection.

These rules do not have to apply to the ConcurrentHashMap.

ConcurrentHashMap will never throw a ConcurrentModificationException. You can remove elements via the iterator or an explicit remove from the CHM itself inside the iterator. I don't know why you would want to, but you could do both.

Caffeine0001a at 2007-7-12 14:57:15 > top of Java-index,Core,Core APIs...