How to make a counter thread-safe
Short answer
AtomicInteger increments the value atomically — as one indivisible operation, even with many threads:
void main() {
var counter = new AtomicInteger();
try (var pool = Executors.newFixedThreadPool(4)) {
for (int i = 0; i < 10_000; i++) {
pool.submit(counter::incrementAndGet);
}
}
IO.println(counter.get());
}Output:
10000synchronized
A synchronized block lets only one thread in at a time. It fits when several values must change together:
class Account {
private int balance;
private int operations;
synchronized void deposit(int amount) {
balance += amount;
operations++;
}
synchronized String summary() {
return balance + " in " + operations + " operations";
}
}
void main() {
var account = new Account();
try (var pool = Executors.newFixedThreadPool(4)) {
for (int i = 0; i < 1000; i++) {
pool.submit(() -> account.deposit(5));
}
}
IO.println(account.summary());
}Output:
5000 in 1000 operationsCounters by key
ConcurrentHashMap.merge() safely updates a value by key:
void main() {
var hits = new ConcurrentHashMap<String, Integer>();
var pages = List.of("home", "blog", "home");
try (var pool = Executors.newFixedThreadPool(4)) {
for (int i = 0; i < 1000; i++) {
String page = pages.get(i % pages.size());
pool.submit(() -> hits.merge(page, 1, Integer::sum));
}
}
IO.println(new TreeMap<>(hits));
}Output:
{blog=333, home=667}Common mistake
Incrementing a plain field with count++ from several threads. The operation consists of a read, an add and a write, and threads overwrite each other's results:
int count = 0;
void main() {
try (var pool = Executors.newFixedThreadPool(4)) {
for (int i = 0; i < 100_000; i++) {
pool.submit(() -> count++);
}
}
IO.println(count);
}Output:
99430The result is almost always less than 100,000 and changes from run to run.
Functions used in this recipe
- AtomicInteger — An integer that can be changed safely from several threads without locks.
- synchronized — Lets only one thread at a time run a block of code.
- ConcurrentHashMap — A thread-safe map for concurrent use from many threads.