100. Потоки, синхронизация и реализация AtomicInteger #
Условие задачи:
📌 Дан код с потоками. Нужно:
Сказать, что выведется в консоль и почему.
Показать корректную реализацию счётчика с помощью
AtomicInteger.Кратко объяснить, в чём проблема текущего кода.
Код:
public class Question9 {
public static class TestClass extends Thread {
private static int threadcounter = 0;
public void run() {
++threadcounter;
System.out.println(threadcounter);
}
}
public static void main(String[] args) throws Exception {
for (int i = 0; i < 10; i++) {
synchronized (TestClass.class) {
new TestClass().start();
}
}
}
}
Спойлеры к решению
Подсказки
synchronized в main не защищает код внутри run().💡
++threadcounter — не атомарная операция.💡 Потоки стартуют асинхронно, порядок выполнения не гарантирован.
💡 Для корректного счётчика нужен
AtomicInteger или синхронизация в run().Решение
1️⃣ Что выведется в консоль? #
👉 Будут выведены числа от 1 до 10, но:
порядок не гарантирован,
могут быть дубликаты,
какие-то числа могут пропасть.
Примеры возможного вывода:
1
2
3
4
5
6
7
8
9
10
или
1
2
2
3
4
6
6
7
9
9
или любой другой непредсказуемый вариант.
2️⃣ Почему так происходит? #
❌ Проблемы текущего кода #
synchronizedиспользуется не там
synchronized (TestClass.class) {
new TestClass().start();
}
Блок синхронизирует только создание и запуск потока,
но не выполнение метода
run().
++threadcounterне атомарен
++threadcounter;
На самом деле это три операции:
чтение значения
увеличение
запись
Если два потока выполняют это одновременно — данные теряются (race condition).
static intбез защиты
Общий для всех потоков.
Нет ни
volatile, ни синхронизации.
3️⃣ Как правильно — реализация через AtomicInteger ✅ #
✔️ Исправленный вариант с AtomicInteger
#
import java.util.concurrent.atomic.AtomicInteger;
public class Question9 {
public static class TestClass extends Thread {
private static final AtomicInteger threadCounter = new AtomicInteger(0);
@Override
public void run() {
int value = threadCounter.incrementAndGet();
System.out.println(value);
}
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new TestClass().start();
}
}
}
🔹 Что гарантирует этот код? #
incrementAndGet()— атомарная операцияКаждое число от 1 до 10 будет выведено ровно один раз
Порядок всё ещё не гарантирован (это нормально для потоков)
4️⃣ Альтернатива (через synchronized) #
public void run() {
synchronized (TestClass.class) {
++threadcounter;
System.out.println(threadcounter);
}
}
✔️ Работает корректно,
❌ Но хуже масштабируется, чем AtomicInteger.
🧠 Коротко, как сказать на собесе #
synchronizedв main не защищаетrun().
Инкрементintне атомарен, поэтому возможны гонки.
Правильное решение —AtomicInteger.incrementAndGet()или синхронизация внутриrun().
Если хочешь — могу ужать ответ до 30 секунд устного объяснения или усложнить вопрос (volatile vs atomic, happens-before).