- get the value from the ConcurrentMap;
- if null, assume it's the first access, and create the value;
- call putIfAbsent on the concurrentMap to store the new value;
- 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.
import java.util.*;To compile and run the SqrtTest (
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;
}
}
-Dconcurrency=123
can be used to adjust the concurrency level):$ javac SqrtTest.javaFrom 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.
$ 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
...
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.