Is using a synchronised block of concurrent hash map correct?

136 Views Asked by At

As per my understanding ConcurrentHashMap will allow multiple threads to read and write (add/remove) on the same hash map with out getting concurrent hash map exception.

I have 4 threads and each thread can update the hashmap. I do not want other threads to write/update on hashmap while the current thread is updating it.

ConcurrentHashMap<String, Integer> playerLoginCounterHashMap = new ConcurrentHashMap<>();

    ExecutorService executorService = Executors.newFixedThreadPool(4);

    for (int i = 0; i < 4; i++) {

        executorService.submit(new Runnable() {
            @Override
            public void run() {
                synchronized (playerLoginCounterHashMap) {
                    if (playerLoginCounterHashMap.get("testPlayer") == null) {
                        playerLoginCounterHashMap.put("testPlayer", 1);
                    } else {
                        playerLoginCounterHashMap.put("testPlayer", playerLoginCounterHashMap.get("testPlayer").intValue() + 1);
                    }
                }
            }
        });
    }

Is this the correct way of doing it? Without synchronised block I get the values which are not correct.

2

There are 2 best solutions below

0
JB Nizet On BEST ANSWER

Yes, it's correct (assuming this is the only place where the map is updated), but it's inefficient, since it synchronizes instead of relying on the inherent non-blocking concurrency of the map.

You should use compute() instead:

playerLoginCounterHashMap.compute(
    "testPlayer",
    (key, value) -> value == null ? 1 : value + 1);

Or merge():

playerLoginCounterHashMap.merge(
    "testPlayer",
    1,
    Integer::sum);
0
yegodm On

Please, note that in a simple case of storing a per-user long counter it might make sense to use Google Guava AtomicLongMap:

final AtomicLongMap<String> loginCounterByPlayerName = AtomicLongMap.create();
final ExecutorService executorService = Executors.newFixedThreadPool(4);
for (int i = 0; i < 4; i++) {
    executorService.submit(new Runnable() {
        @Override
        public void run() {
            loginCounterByPlayerName.addAndGet("testPlayer", 1);
        }
    });
}

The only different thing is that counter starts from 0.