Разберите этот фрагмент Java (потоки): "volatile int counter; void inc(){ counter++; }" Объясните, почему volatile тут недостаточен для потокобезопасности и как правильно синхронизировать

15 Янв в 13:08
10 +1
0
Ответы
1
Кратко: volatile гарантирует видимость и порядок доступа, но не атомарность. Операция counter++counter++counter++ — это не единичная атомарная инструкция, а минимум три шага: чтение, вычисление, запись. Поэтому при конкуренции значений будут теряться.
Почему volatile недостаточен
- counter++counter++counter++ разворачивается примерно так: читать текущее значение, вычислить новое, записать обратно — т.е. последовательность: чтение →\to инкремент →\to запись. Volatile гарантирует, что чтения/записи видимы другим потокам и упорядочены, но не делает этот трёхшаговый сценарий невзаимоисключающим.
- Пример гонки: начальное значение counter=0\,counter = 0counter=0. Два потока читают 0\,00 одновременно, оба записывают 1\,11. Итог — одно приращение потеряно (ожидали 2\,22, получили 1\,11).
Как правильно синхронизировать
1) synchronized (простой и корректный способ)
```java
class C {
private int counter = 0;
public synchronized void inc() { counter++; }
public synchronized int get() { return counter; }
}
```
Монитор обеспечивает атомарность и видимость; поле не обязано быть volatile.
2) java.util.concurrent.atomic.AtomicInteger (лучше по производительности при низкой/средней конкуренции)
```java
import java.util.concurrent.atomic.AtomicInteger;
class C {
private final AtomicInteger counter = new AtomicInteger(0);
public void inc() { counter.incrementAndGet(); } // или getAndIncrement()
public int get() { return counter.get(); }
}
```
3) LongAdder (высокая конкуренция, часто быстрее для счётчиков «только инкремент»)
```java
import java.util.concurrent.atomic.LongAdder;
class C {
private final LongAdder counter = new LongAdder();
public void inc() { counter.increment(); }
public long get() { return counter.sum(); }
}
```
4) Явные блокировки
```java
import java.util.concurrent.locks.ReentrantLock;
class C {
private final ReentrantLock lock = new ReentrantLock();
private int counter = 0;
public void inc() {
lock.lock();
try { counter++; } finally { lock.unlock(); }
}
}
```
Когда volatile подходит
- Для простых флагов «прочитал/записал» (например, сигнал остановки), где операции — одиночные чтение/запись. Для составных операций (инкремент, проверка+изменение и т.п.) нужен механизм атомарности/блокировки.
Резюме: оставлять только volatile для инкремента нельзя — используйте synchronized, AtomicInteger/LongAdder или блокировки в зависимости от потребностей по производительности и семантике.
15 Янв в 13:15
Не можешь разобраться в этой теме?
Обратись за помощью к экспертам
Гарантированные бесплатные доработки в течение 1 года
Быстрое выполнение от 2 часов
Проверка работы на плагиат
Поможем написать учебную работу
Прямой эфир