Reputation: 1903
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
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
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