← Назад к вопросам

Что произойдет при непотокобезопасной инкрементации переменной?

2.0 Middle🔥 121 комментариев
#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Непотокобезопасная инкрементация переменной

Проблема

Когда несколько потоков одновременно инкрементируют одну и ту же переменную без синхронизации, возникает race condition (гонка потоков). Операция инкрементации i++ выглядит как одна операция, но на самом деле состоит из трёх отдельных шагов:

  1. Чтение текущего значения
  2. Увеличение значения на 1
  3. Запись нового значения

Что произойдёт

Если два потока выполняют эти шаги одновременно, они могут потерять обновления. Вот пример:

public class NonThreadSafeCounter {
    private int count = 0;
    
    public void increment() {
        count++;  // НЕ атомарная операция!
    }
    
    public int getCount() {
        return count;
    }
}

Сценарий гонки:

  • Поток 1 читает count = 0
  • Поток 2 читает count = 0 (перед тем, как поток 1 записал!)
  • Поток 1 увеличивает, записывает count = 1
  • Поток 2 увеличивает, записывает count = 1
  • Результат: count = 1, вместо ожидаемых 2

Последствия

  • Потеря данных — обновления игнорируются
  • Неопределённое поведение — результат зависит от времени выполнения
  • Трудная отладка — ошибка проявляется редко и непредсказуемо
  • Нарушение инвариантов — если счётчик должен быть точным, он становится неправильным

Решения

1. Использование synchronized блока

public class ThreadSafeCounter {
    private int count = 0;
    
    public synchronized void increment() {
        count++;
    }
    
    public synchronized int getCount() {
        return count;
    }
}

2. Использование AtomicInteger (рекомендуется)

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicCounter {
    private AtomicInteger count = new AtomicInteger(0);
    
    public void increment() {
        count.incrementAndGet();  // Атомарная операция
    }
    
    public int getCount() {
        return count.get();
    }
}

3. Использование volatile (только для чтения/записи)

public class VolatileCounter {
    private volatile int count = 0;
    
    public void increment() {
        count++;  // ВСЁ ЕЩЁ не потокобезопасна!
    }
}

Важно: volatile гарантирует видимость изменений, но не атомарность операции. Для i++ этого недостаточно.

Пример с потоками

public class RaceConditionDemo {
    private int counter = 0;
    
    public static void main(String[] args) throws InterruptedException {
        RaceConditionDemo demo = new RaceConditionDemo();
        
        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    demo.counter++;  // Гонка!
                }
            });
            threads[i].start();
        }
        
        for (Thread t : threads) {
            t.join();
        }
        
        System.out.println("Expected: 10000, Actual: " + demo.counter);
        // Выведет число меньше 10000, например: 8254
    }
}

Вывод

Непотокобезопасная инкрементация приводит к потере обновлений из-за race condition. Используй AtomicInteger для простых счётчиков или synchronized для более сложной логики. Всегда помни, что i++ состоит из трёх отдельных операций!