В многопоточном Java-коде есть два способа изменить общую переменную.
Способ 1: `synchronized` (Пессимистичный)
В процессоре эта «очередь» сжигает ресурсы: потоки засыпают и просыпаются, ядро постоянно переключается между ними.
Способ 2: CAS — Compare-And-Swap (Оптимистичный)
Работает по принципу «попробуй и проверь».
Процесс выглядит так:
1. Поток читает текущее значение из памяти
(например, видит число 5).
2. Поток вычисляет новое значение
(5 + 1 = 6).
3. Поток дает команду процессору:
«Замени это значение на 6, но только если оно всё еще равно 5. Если за то время, пока я считал, кто-то уже успел записать туда другое число — не делай ничего и сообщи мне об ошибке».
Если запись удалась — отлично.
Если нет (кто-то вмешался) — поток просто повторяет всю операцию заново с новым значением. Это происходит очень быстро прямо внутри ядра CPU, обращения к операционной системе не требуется.
Как это выглядит в коде
Вам не нужно писать логику цикла вручную. Для этого есть классы из пакета `java.util.concurrent.atomic`.
// Обычная переменная (небезопасна)
private int counter = 0;
// Атомарная переменная (использует CAS под капотом)
private final AtomicInteger atomicCounter = new AtomicInteger(0);
public void increment() {
// Внутри этой строки скрыт цикл "прочитал-попробовал-снова"
atomicCounter.incrementAndGet( );
}
Метод compareAndSet(int expect, int update) делает ровно то, что описано выше:
* expect — то, что мы прочитали мгновение назад.
* update — то, что хотим положить взамен.
* Возвращает true, если удалось заменить, или false, если значение в памяти успело измениться.
Где у CAS проблемы (ABA Problem)
У оптимистичного подхода есть один изъян. Допустим, в переменной лежит объект A.
1. Поток-1 прочитал адрес объекта A.
2. Произошла пауза. Другой поток удалил A.
3. Система выделила ту же самую ячейку памяти под совершенно новый объект и снова назвала его A.
4. Вернулся Поток-1. Он делает проверку: «Там все еще A? Да. Пишу свое».