Какие типы данных в Java не гарантируют атомарность операций
Что проверяют
Знаете ли вы, что гарантирует модель памяти Java, и отличаете ли атомарность отдельной записи от атомарности операции. Следом обычно спрашивают, спасёт ли volatile счётчик count++.
Как отвечать
Ответ из спецификации (JLS 17.7): чтение и запись переменных всех примитивов и ссылок атомарны, кроме long и double без volatile. Их 64-битная запись может разбиться на две половины по 32 бита, и другой поток прочитает «половинчатое» значение. На 64-битных JVM на практике так не бывает, но спецификация этого не обещает. Модификатор volatile делает запись long и double атомарной.
Вторая, более важная часть: неатомарны составные операции над любым типом.
- инкремент и
+=:count++это чтение, сложение и запись, два потока теряют обновления даже наvolatile int; - check-then-act:
if (!map.containsKey(k)) map.put(k, v)ломается между проверкой и записью; - коллекции:
HashMapиArrayListвовсе не потокобезопасны, аCollections.synchronizedMapзащищает только отдельные вызовы, не их комбинацию и не итерацию.
Инструменты по задаче:
AtomicInteger,AtomicLong,AtomicReference: CAS-операцииincrementAndGet,compareAndSet,updateAndGet;LongAdder: счётчик под сильной конкуренцией, раскидывает инкременты по ячейкам и выигрывает уAtomicLong;ConcurrentHashMap: атомарныеputIfAbsent,compute,merge;synchronizedиReentrantLock: когда нужно атомарно изменить несколько полей сразу.
Где спотыкаются
Путают атомарность и видимость. volatile гарантирует, что поток увидит свежее значение и порядок операций, но count++ на нём остаётся гонкой.
Утверждают, что int в целом неатомарен. Отдельные чтение и запись int атомарны, проблема только в составных операциях.
Читают AtomicInteger через get(), а потом вызывают set(). Две атомарные операции подряд не образуют третью, для этого есть compareAndSet и updateAndGet.