Search code examples
javamultithreadinghashmapconcurrenthashmap

Java Concurrency


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)]);
        }
    }
} 

Solution

  • 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.