Что произойдет при непотокобезопасной инкрементации переменной?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Непотокобезопасная инкрементация переменной
Проблема
Когда несколько потоков одновременно инкрементируют одну и ту же переменную без синхронизации, возникает race condition (гонка потоков). Операция инкрементации i++ выглядит как одна операция, но на самом деле состоит из трёх отдельных шагов:
- Чтение текущего значения
- Увеличение значения на 1
- Запись нового значения
Что произойдёт
Если два потока выполняют эти шаги одновременно, они могут потерять обновления. Вот пример:
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++ состоит из трёх отдельных операций!