3/05/2012

ConcurrentHashMap Examples

4 steps when accessing a cache implemented with java.util.ConcurrentHashMap (javadoc):

  1. get the value from the ConcurrentMap;
  2. if null, assume it's the first access, and create the value;
  3. call putIfAbsent on the concurrentMap to store the new value;
  4. if return value is not null (it's rare but happens), use the return value as the golden copy, and discard the newly-created object.
The following test class, SqrtTest, displays the square root of numbers, and each square root value is accessed multiple times concurrently. The intent is to calculate it only on the first access and return the cached value for subsequent requests.
import java.util.*;
import java.util.concurrent.*;

public class SqrtTest {
private static final String CONCURRENCY_LEVEL_DEFAULT = "50";
private static final String CONCURRENCY_KEY = "concurrency";
private ConcurrentMap<Double, Double> sqrtCache = new ConcurrentHashMap<Double, Double>();

public static void main(String args[]) {
final SqrtTest test = new SqrtTest();
final int concurrencyLevel = Integer.parseInt(System.getProperty(CONCURRENCY_KEY, CONCURRENCY_LEVEL_DEFAULT));
final ExecutorService executor = Executors.newCachedThreadPool();

try {
for(int i = 0; i < concurrencyLevel; i++) {
for(String s : args) {
final Double d = Double.valueOf(s);
executor.submit(new Runnable() {
@Override public void run() {
System.out.printf("sqrt of %s = %s in thread %s%n",
d, test.getSqrt(d), Thread.currentThread().getName());
}
});
}
}
} finally {
executor.shutdown();
}
}

// 4 steps as outlined above
public double getSqrt(Double d) {
Double sqrt = sqrtCache.get(d);
if(sqrt == null) {
sqrt = Math.sqrt(d);
System.out.printf("calculated sqrt of %s = %s%n", d, sqrt);
Double existing = sqrtCache.putIfAbsent(d, sqrt);
if(existing != null) {
System.out.printf("discard calculated sqrt %s and use the cached sqrt %s", sqrt, existing);
sqrt = existing;
}
}
return sqrt;
}
}
To compile and run the SqrtTest (-Dconcurrency=123 can be used to adjust the concurrency level):
$ javac SqrtTest.java
$ java SqrtTest 0.5 11 999 0.1

calculated sqrt of 0.5 = 0.7071067811865476
sqrt of 0.5 = 0.7071067811865476 in thread pool-1-thread-1
calculated sqrt of 11.0 = 3.3166247903554
sqrt of 11.0 = 3.3166247903554 in thread pool-1-thread-2
calculated sqrt of 999.0 = 31.606961258558215
sqrt of 999.0 = 31.606961258558215 in thread pool-1-thread-1
sqrt of 11.0 = 3.3166247903554 in thread pool-1-thread-2
calculated sqrt of 0.1 = 0.31622776601683794
calculated sqrt of 0.1 = 0.31622776601683794
sqrt of 999.0 = 31.606961258558215 in thread pool-1-thread-1
sqrt of 11.0 = 3.3166247903554 in thread pool-1-thread-8
sqrt of 0.5 = 0.7071067811865476 in thread pool-1-thread-4
sqrt of 0.5 = 0.7071067811865476 in thread pool-1-thread-7 calculated sqrt of 0.1 = 0.31622776601683794
discard calculated sqrt 0.31622776601683794 and use the cached sqrt 0.31622776601683794sqrt of 0.1 = 0.31622776601683794 in thread pool-1-thread-6
...
From the above output, we can see at least one calculation is discarded since the value already exists in the cache. It had been added to the cache by another thread between step 1 and step 3.

Multiple input double numbers are used to increase thread contention. When testing with one single input number, I couldn't trigger the race condition as evidenced by the "discard calculated sqrt" log message. It is probably because it takes time for the thread pool to create the second thread, and by the time it kicks in, the result is already calculated by the first thread and well established in the cache.

6 comments:

Anonymous said...

Fantastic example. Its also good to know differences between HashMap and ConcurrentHashMap in Java , which helps to decide when to use CHM

Satish said...

See http://javaopensourcecode.blogspot.com/2012/06/concurrenthashmap.html
Article describes in detail about the internals of HashMap and CocurrentHashMap

Pierre-Hugues Charbonneau said...

Very good article on ConcurrentHashMap. Your readers may be interested in performance of ConcurrentHashMap vs. HashMap as well as thread safety concerns.

Thanks.
P-H

Java Proficiency said...

Nice Article. visit more java hashmap examples

Anna said...

Great and Useful Article.

Online Java Training

Java Online Training India

Java Online Course

Java EE course

Java EE training

Best Recommended books for Spring framework

Java Interview Questions








Java Course in Chennai

Java Online Training India

BloggyOatmeal said...

I believe the DISCARD race condition is occurring because of the printf for the calculated sqrt above the exists test. Comment it out and see that there are no longer race conditions (at least for me on my Mac OS). Then replace that printf with a Thread.sleep and increase from 1 ms to 100ms and see the DISCARD race condition error increase along with the time delay.