Какой будет результат при инкременте int переменной несколькими потоками?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Race Condition при инкременте переменной несколькими потоками
Это классический пример проблемы с многопоточностью. Инкремент i++ — это не атомарная операция, что приводит к потере данных.
Проблема: Инкремент не атомарен
Операция i++ выглядит просто, но на низком уровне состоит из трёх действий:
1. Прочитать текущее значение i (READ)
2. Увеличить значение на 1 (COMPUTE)
3. Записать результат обратно (WRITE)
Демонстрация проблемы
public class RaceConditionExample {
private int counter = 0; // НЕ синхронизирован!
public void increment() {
counter++; // Это NOT ATOMIC!
}
public int getCounter() {
return counter;
}
public static void main(String[] args) throws InterruptedException {
RaceConditionExample example = new RaceConditionExample();
// Создаём 10 потоков
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(() -> {
// Каждый поток инкрементит 10000 раз
for (int j = 0; j < 10000; j++) {
example.increment();
}
});
}
// Запускаем все потоки
for (Thread t : threads) {
t.start();
}
// Ждём завершения
for (Thread t : threads) {
t.join();
}
// Ожидаемый результат: 100000
// Фактический результат: 87234 (или другое число < 100000)
System.out.println("Counter: " + example.getCounter());
}
}
Ожидаемый результат: 100000 (10 потоков × 10000 инкрементов)
Фактический результат: ~87000-90000 (число будет разным при каждом запуске)
Почему результат неправильный?
У нас есть race condition — условие гонки:
Поток 1 Поток 2 counter в памяти
Разницу:
i = 5 (READ) - 5
i = 5 (READ) 5
i = 6 (WRITE) - 6
i = 6 (WRITE) 6
Оба потока прочитали значение 5, оба увеличили его до 6 и записали. Один инкремент потеряется!
Решение 1: synchronized
Делаем метод синхронизированным — только один поток может выполняться одновременно:
public class SynchronizedExample {
private int counter = 0;
public synchronized void increment() {
counter++; // Теперь атомарно!
}
public synchronized int getCounter() {
return counter;
}
}
Как это работает:
- Каждый объект имеет встроенный монитор (lock)
- Только один поток может одновременно выполнять
synchronizedметоды одного объекта - Другие потоки ждут в очереди
Недостаток: Может быть медленнее при высокой конкуренции
Решение 2: AtomicInteger (лучше всего)
Используем AtomicInteger — специальный класс для атомарных операций:
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicExample {
private AtomicInteger counter = new AtomicInteger(0);
public void increment() {
counter.incrementAndGet(); // Атомарная операция!
}
public int getCounter() {
return counter.get();
}
public static void main(String[] args) throws InterruptedException {
AtomicExample example = new AtomicExample();
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 10000; j++) {
example.increment();
}
});
}
for (Thread t : threads) t.start();
for (Thread t : threads) t.join();
// Результат: ровно 100000
System.out.println("Counter: " + example.getCounter());
}
}
Преимущества:
- Использует Compare-And-Swap (CAS) вместо блокировок
- Лучше для высокой конкуренции
- Не создаёт очередей ожидания
Решение 3: Lock (ReentrantLock)
Для большей гибкости используем явные блокировки:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
private int counter = 0;
private final Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
counter++;
} finally {
lock.unlock();
}
}
public int getCounter() {
lock.lock();
try {
return counter;
} finally {
lock.unlock();
}
}
}
Решение 4: Использование volatile (не достаточно!)
ВАЖНО: volatile НЕ решает проблему для операции i++:
private volatile int counter = 0;
public void increment() {
counter++; // Всё ещё race condition!
}
volatile гарантирует видимость, но не атомарность. i++ по-прежнему состоит из трёх операций.
Сравнение подходов
| Подход | Скорость | Простота | Когда использовать |
|---|---|---|---|
| synchronized | Средняя | Простая | Простые методы, низкая конкуренция |
| AtomicInteger | Быстро | Простая | Счётчики, высокая конкуренция |
| ReentrantLock | Средняя | Сложно | Сложная синхронизация, условия |
| volatile | Быстро | Очень просто | Только флаги, не операции |
Итоговая ответ
При инкременте int переменной несколькими потоками БЕЗ синхронизации:
- Результат: Неправильное (меньшее) значение
- Причина: Race condition,
i++не атомарна - Решение: Использовать
synchronized,AtomicIntegerилиLock - Лучший выбор:
AtomicIntegerдля счётчиков,synchronizedдля простых случаев