Как сделать счётчик потокобезопасным
Короткий ответ
AtomicInteger увеличивает значение атомарно — как одну неделимую операцию, даже если потоков много:
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());
}Результат:
10000synchronized
Блок synchronized пускает внутрь только один поток за раз. Это подходит, когда нужно изменить несколько значений вместе:
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());
}Результат:
5000 in 1000 operationsСчётчики по ключам
ConcurrentHashMap.merge() безопасно обновляет значение по ключу:
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));
}Результат:
{blog=333, home=667}Частая ошибка
Увеличивать обычное поле через count++ из нескольких потоков. Операция состоит из чтения, сложения и записи, и потоки затирают результаты друг друга:
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);
}Результат:
99430Результат почти всегда меньше 100 000 и меняется от запуска к запуску.
Функции из этого ответа
- AtomicInteger — Целое число, которое можно безопасно менять из нескольких потоков без блокировок.
- synchronized — Разрешает выполнять блок кода только одному потоку за раз.
- ConcurrentHashMap — Потокобезопасный словарь для одновременной работы из многих потоков.