I'm trying to implement some kind of accumulation logic in a multi threaded environment; I’m wondering is there any better/faster way to do it without the lock and synchronized keyword? The following is my current code:
public class ConcurrentHashMapTest {
private static final int NB_THREADS = 1_000;
private final Map<String, Integer> cnts = new HashMap<>();
private static final Lock RWLOCK = new ReentrantLock(true);
private static final String[] NAMES = {
"A", "B"
};
public void testIt() {
ExecutorService executor =
Executors.newFixedThreadPool(NB_THREADS);
for (int i = 0; i < NB_THREADS; i++) {
Runnable task = new WorkerThread();
executor.submit(task);
}
executor.shutdown();
try {
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println(cnts);
}
private void accumulate(String name) {
RWLOCK.lock();
try {
Integer cnt = cnts.get(name);
if (cnt == null) {
cnts.put(name, 1);
} else {
cnts.put(name, cnt + 1);
}
} finally {
RWLOCK.unlock();
}
}
private class WorkerThread implements Runnable {
@Override
public void run() {
accumulate(NAMES[ThreadLocalRandom.current().nextInt(0, NAMES.length)]);
}
}
}
Java 8:
private final Map<String, AtomicInteger> cnts =
new ConcurrentHashMap<>();
private void accumulate(String name) {
cnts.computeIfAbsent(name, k -> new AtomicInteger()).incrementAndGet();
}
The ConcurrentHashMap
can be freely accessed from multiple threads. The computeIfAbsent
method takes a lambda to evaluate to get a value for the key if the key is not present in the map, and adds it if and only if there is no such mapping, and then returns that value. It's effectively putIfAbsent
followed by get
. The value is a new AtomicInteger
with the value 0. Whether there was an existing value, or whether a new one with value 0 was just added, in either case increment it.
Java 7:
private final ConcurrentMap<String, AtomicInteger> cnts =
new ConcurrentHashMap<>();
private void accumulate(String name) {
cnts.putIfAbsent(name, new AtomicInteger());
cnts.get(name).incrementAndGet();
}
For Java 7, there is no computeIfAbsent
method, but that effectively just does a putIfAbsent
followed by a get
, so the same effect is achieved by calling those methods. There is no concern that the value already existed in the map; a new, zero AtomicInteger
is added if and only if the map had no value for that key. Even if another thread got in there before us and added a zero, both threads would then see and increment that same AtomicInteger
instance.