The 30 second answer
Reads usually don't block, and writes lock only a small part of the table, so many threads work at once. Each single call is atomic, and compute, computeIfAbsent and merge are atomic per key. But check-then-act across two calls can still race, so I use putIfAbsent or compute instead. Size and iteration are only estimates during writes, and null keys or values are not allowed.
Reads usually don't block, and updates lock only a small part of the table, so many threads can work at once. Each single call is atomic, and compute, computeIfAbsent and merge run atomically per key. But a sequence like if (!map.containsKey(k)) map.put(k, v) is two calls and can race; use putIfAbsent or compute. size() and iteration are only estimates while others write, and it doesn't allow null keys or values. Follow-up: How do you count frequencies safely? (map.computeIfAbsent(k, x -> new LongAdder()).increment(), the example in the Javadoc.)