Task Livecoding Java Atomic Integer and Threads

100. Потоки, синхронизация и реализация AtomicInteger #

Условие задачи:
📌 Дан код с потоками. Нужно:

  1. Сказать, что выведется в консоль и почему.

  2. Показать корректную реализацию счётчика с помощью AtomicInteger.

  3. Кратко объяснить, в чём проблема текущего кода.

Код:

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️⃣ Почему так происходит? #

❌ Проблемы текущего кода #

  1. synchronized используется не там
synchronized (TestClass.class) {
    new TestClass().start();
}
  • Блок синхронизирует только создание и запуск потока,

  • но не выполнение метода run().

  1. ++threadcounter не атомарен
++threadcounter;

На самом деле это три операции:

  1. чтение значения

  2. увеличение

  3. запись

Если два потока выполняют это одновременно — данные теряются (race condition).

  1. 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).