Max
Max

Reputation: 1903

Can I use ConcurrentHashMap with Integer for thread safe counters?

I want to have several counters, which I can address by name. So, I can implement it in this way:

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.computeIfAbsent("key", k -> new Integer(0));
map.computeIfPresent("key", (k, v) -> v + 1);

Is it code a thread safe?

I think so, because we have synchronized access because of ConcurrentHashMap, and set new reference is thread safe operation too. Other threads will see this changing because of safe publication, which occurs when we leave bucket lock in ConcurrentHashMap.

Upvotes: 1

Views: 878

Answers (2)

devoured elysium
devoured elysium

Reputation: 105177

Assuming that the first statement, map.computeIfAbsent("key", k -> new Integer(0)); happens at "initialization time", then having a bunch of threads performing calls of the form map.computeIfPresent("key", (k, v) -> v + 1);, yes, the algorithm is going to be correct (did I understand well your intent?).

Recent versions of the JDK guarantee that calls to ConcurrentHashMap.computeIfPresent() will not only invoke the expression passed in in a thread-safe way, it will also guarantee that if other threads attempt to act on that very same key at the same time, they'll block and get queued, such that all mutations happen in sequence (this is called serializability in distributed-systems parlance).

Upvotes: 1

dung ta van
dung ta van

Reputation: 1026

Yes, it's threadsafe, you can test:

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

public class ThreadsafeExample {

    public static void main(String[] args) throws Exception {
        ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();

        int count = 1000;
        AtomicInteger doneTasks = new AtomicInteger();
        Thread[] threads = new Thread[count];
        for(int i = 0 ; i < threads.length ; ++i) {
            threads[i] = new Thread(() -> {
                map.computeIfAbsent("key", k -> new Integer(0));
                map.computeIfPresent("key", (k, v) -> v + 1);
                doneTasks.incrementAndGet();
            });
        }
        for(int i = 0 ; i < threads.length ; ++i)
            threads[i].start();

        while (doneTasks.get() < count)
            Thread.sleep(3);

        System.out.println("we expected count of key is: " + count + ", and we get: " + map.get("key"));
    }

}

output:

we expected count of key is: 1000, and we get: 1000

You can replace:

map.computeIfAbsent("key", k -> new Integer(0));
map.computeIfPresent("key", (k, v) -> v + 1);

by

map.compute("key", (k, v) -> v == null ? 1 : v + 1);

Upvotes: 3

Related Questions