Encodera

Thanks for reading!

Donate to the site — we work hard for you.

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:

10000

synchronized

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 operations

Counters 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:

99430

The 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.

Thanks for reading!

Donate to the site — we work hard for you.