← Назад к вопросам

Что такое атомики?

1.8 Middle🔥 161 комментариев
#JVM и память#Многопоточность и асинхронность

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Атомики (Atomic Operations)

Атомики (атомарные операции) — это низкоуровневые операции над данными, которые выполняются неразрывно (неделимо) с точки зрения других потоков выполнения. Если поток выполняет атомарную операцию, никакой другой поток не может наблюдеть промежуточное состояние этой операции — она либо уже завершилась, либо ещё не начиналась. Это фундаментальный механизм для обеспечения потокобезопасности (thread safety) без использования тяжеловесных блокировок (например, synchronized или ReentrantLock).

Основные свойства атомарных операций

  1. Неделимость (Atomicity) — операция выполняется целиком, без возможности прерывания другим потоком.
  2. Видимость (Visibility) — результат атомарной операции немедленно становится видимым всем потокам (благодаря использованию volatile-семантики и барьеров памяти).
  3. Упорядочивание (Ordering) — многие атомарные операции обеспечивают гарантии порядка выполнения инструкций (memory ordering), предотвращая нежелательную перестановку операций компилятором или процессором.

Зачем нужны атомики?

В многопоточных приложениях при одновременном доступе к общим данным возникают состояния гонки (race conditions). Классический пример — инкремент счётчика:

// НЕПРАВИЛЬНО: потоконебезопасный инкремент
var counter = 0

fun increment() {
    counter++ // Неатомарная операция: read-modify-write
}

Операция counter++ состоит из трёх этапов:

  1. Чтение текущего значения
  2. Увеличение на единицу
  3. Запись нового значения

Два потока могут прочитать одно и то же значение, увеличить его и записать, что приведёт к потере одного инкремента.

Атомики решают эту проблему, гарантируя, что вся операция read-modify-write выполнится атомарно.

Атомики в Android/Java (java.util.concurrent.atomic)

В Java и Kotlin атомарные операции представлены классами в пакете java.util.concurrent.atomic:

Основные классы:

  • AtomicInteger, AtomicLong, AtomicBoolean — для примитивных типов
  • AtomicReference — для ссылочных типов
  • AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray — для массивов
  • AtomicStampedReference, AtomicMarkableReference — для решения проблемы ABA

Пример использования AtomicInteger:

import java.util.concurrent.atomic.AtomicInteger

val atomicCounter = AtomicInteger(0)

fun safeIncrement() {
    atomicCounter.incrementAndGet() // Атомарный инкремент
}

fun complexUpdate() {
    // Атомарное обновление с использованием функции
    atomicCounter.updateAndGet { currentValue ->
        if (currentValue < 10) currentValue + 2 else currentValue
    }
}

Как работают атомики "под капотом"?

Атомарные классы используют аппаратную поддержку процессора — инструкции CAS (Compare-And-Swap):

// Принцип работы CAS (псевдокод)
fun compareAndSwap(expectedValue: Int, newValue: Int): Boolean {
    // Атомарно: если текущее значение == expectedValue, 
    // то установить newValue и вернуть true
    // Иначе ничего не менять и вернуть false
}

Реализация в AtomicInteger.incrementAndGet() использует цикл CAS:

// Упрощённая реализация из OpenJDK
public final int incrementAndGet() {
    for (;;) {
        int current = get();
        int next = current + 1;
        if (compareAndSet(current, next))
            return next;
    }
}

Сравнение с другими подходами

ПодходПреимуществаНедостатки
АтомикиВысокая производительность, нет блокировок, deadlock-безопасностьСложно использовать для сложных составных операций
Синхронизация (synchronized)Простота использования, подходит для сложных операцийНизкая производительность при высокой конкуренции, риск deadlock
Lock APIГибкость (tryLock, timed lock и т.д.)Требует аккуратного управления, риск забыть unlock()

Практическое применение в Android

  1. Счётчики и метрики — подсчёт количества выполненных операций, ошибок
  2. Флаги состояния — атомарное переключение состояний (инициализация, завершение)
  3. Обновление shared preferences — атомарные операции с настройками
  4. Кеширование — атомарное обновление кешированных данных
  5. Id генераторы — потокобезопасная генерация уникальных идентификаторов
// Пример: потокобезопасный синглтон с атомарной инициализацией
class SettingsManager private constructor() {
    companion object {
        private val INSTANCE = AtomicReference<SettingsManager?>(null)
        
        fun getInstance(): SettingsManager {
            while (true) {
                val current = INSTANCE.get()
                if (current != null) return current
                
                if (INSTANCE.compareAndSet(null, SettingsManager())) {
                    return INSTANCE.get()!!
                }
            }
        }
    }
    
    private val config = AtomicReference<Config>(Config.default())
    
    fun updateConfig(newConfig: Config) {
        config.set(newConfig)
    }
}

Ограничения и предостережения

  1. Проблема ABA — значение может измениться с A на B и обратно на A, что может пройти незамеченным (решается через AtomicStampedReference)
  2. Составные операции — несколько атомарных операций вместе не являются атомарными
  3. Производительность при высокой конкуренции — CAS-циклы могут долго повторяться при одновременном доступе многих потоков

Атомики — это мощный инструмент для создания высокопроизводительных потокобезопасных структур данных, но они требуют понимания принципов работы многопоточности и тщательного проектирования.