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

Какие знаешь интерфейсы, гарантирующие атомарность?

1.7 Middle🔥 131 комментариев
#Коллекции#Многопоточность

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

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

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

Интерфейсы и классы, гарантирующие атомарность

Атомарные операции — критичны для потокобезопасности без явных блокировок (lock-free programming). Разберу все основные средства Java для атомарных операций.

1. AtomicInteger

Атомарная обертка над int:

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerExample {
    private final AtomicInteger counter = new AtomicInteger(0);
    
    // Атомарное увеличение
    public void increment() {
        counter.incrementAndGet(); // Возвращает новое значение
        // или
        counter.getAndIncrement(); // Возвращает старое значение
    }
    
    // Атомарное чтение
    public int getCount() {
        return counter.get();
    }
    
    // Атомарное присваивание
    public void reset() {
        counter.set(0);
    }
    
    // Compare-and-swap (CAS) — основа атомарности
    public boolean compareAndSet(int expected, int newValue) {
        return counter.compareAndSet(expected, newValue);
    }
    
    // Атомарное прибавление
    public void add(int value) {
        counter.addAndGet(value);
    }
    
    public static void main(String[] args) throws InterruptedException {
        AtomicIntegerExample example = new AtomicIntegerExample();
        
        // Несколько потоков инкрементируют, нет race conditions
        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    example.increment();
                }
            });
            threads[i].start();
        }
        
        for (Thread t : threads) {
            t.join();
        }
        
        // Результат ВСЕГДА 10000 (без race conditions)
        System.out.println("Count: " + example.getCount());
    }
}

2. AtomicLong

Атомарная обертка над long (для больших чисел):

import java.util.concurrent.atomic.AtomicLong;

public class AtomicLongExample {
    // Счетчик для очень больших значений
    private final AtomicLong totalBytes = new AtomicLong(0);
    
    public void recordDataTransfer(long bytes) {
        totalBytes.addAndGet(bytes);
    }
    
    public long getTotalBytes() {
        return totalBytes.get();
    }
    
    // Timestamp счетчик
    private final AtomicLong lastUpdateTime = new AtomicLong(0);
    
    public void updateTimestamp(long time) {
        lastUpdateTime.set(time);
    }
}

3. AtomicBoolean

Атомарная обертка над boolean (для флагов):

import java.util.concurrent.atomic.AtomicBoolean;

public class AtomicBooleanExample {
    private final AtomicBoolean initialized = new AtomicBoolean(false);
    private final AtomicBoolean shutdownRequested = new AtomicBoolean(false);
    
    // Гарантирует что инициализация произойдет ровно один раз
    public void initialize() {
        if (initialized.compareAndSet(false, true)) {
            // Только первый поток выполнит это
            System.out.println("Initializing...");
            expensiveInitialization();
        }
    }
    
    // Graceful shutdown
    public void shutdown() {
        shutdownRequested.set(true);
    }
    
    public void mainLoop() {
        while (!shutdownRequested.get()) {
            // Работаем пока не запрошено завершение
            doWork();
        }
    }
    
    private void expensiveInitialization() {}
    private void doWork() {}
}

4. AtomicReference

Атомарная ссылка на объект (для неизменяемых объектов):

import java.util.concurrent.atomic.AtomicReference;

public class AtomicReferenceExample {
    static class Configuration {
        String host;
        int port;
        
        Configuration(String host, int port) {
            this.host = host;
            this.port = port;
        }
    }
    
    // Атомарное обновление конфигурации
    private final AtomicReference<Configuration> config = 
        new AtomicReference<>(new Configuration("localhost", 8080));
    
    public Configuration getConfig() {
        return config.get();
    }
    
    // Обновление конфигурации без блокировки
    public void updateConfig(String newHost, int newPort) {
        config.set(new Configuration(newHost, newPort));
    }
    
    // Compare-and-swap для обновления
    public boolean compareAndUpdateConfig(Configuration expected, 
                                          String newHost, int newPort) {
        return config.compareAndSet(expected, 
                                   new Configuration(newHost, newPort));
    }
}

5. AtomicIntegerArray и AtomicLongArray

Атомарные массивы:

import java.util.concurrent.atomic.AtomicIntegerArray;
import java.util.concurrent.atomic.AtomicLongArray;

public class AtomicArrayExample {
    // Массив счетчиков (много потоков могут писать в разные ячейки)
    private final AtomicIntegerArray counters = new AtomicIntegerArray(10);
    
    public void incrementCounter(int index) {
        counters.incrementAndGet(index);
    }
    
    public int getCounter(int index) {
        return counters.get(index);
    }
    
    // Атомарное обновление элемента массива
    public boolean compareAndSet(int index, int expected, int newValue) {
        return counters.compareAndSet(index, expected, newValue);
    }
    
    // Более эффективно чем synchronized массив
    // Каждый элемент имеет свой "lock" (CAS операция)
}

6. AtomicReferenceFieldUpdater

Для атомарного обновления полей в объекте:

import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

public class AtomicFieldUpdaterExample {
    static class Node {
        volatile Node next;
        String data;
        
        Node(String data) {
            this.data = data;
        }
    }
    
    // Updater для поля Node.next
    private static final AtomicReferenceFieldUpdater<Node, Node> updater =
        AtomicReferenceFieldUpdater.newUpdater(Node.class, Node.class, "next");
    
    public boolean addNode(Node head, Node newNode) {
        // Атомарно обновляем next в голове
        while (true) {
            Node next = updater.get(head);
            newNode.next = next;
            
            if (updater.compareAndSet(head, next, newNode)) {
                return true; // Успешно добавили
            }
            // Повторяем если не получилось (retry)
        }
    }
}

7. AtomicIntegerFieldUpdater и AtomicLongFieldUpdater

Для атомарного обновления примитивных полей:

import java.util.concurrent.atomic.AtomicLongFieldUpdater;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

public class AtomicFieldUpdaterPrimitiveExample {
    static class Counter {
        // Поле должно быть volatile
        volatile long value = 0;
        volatile int count = 0;
    }
    
    private static final AtomicLongFieldUpdater<Counter> valueUpdater =
        AtomicLongFieldUpdater.newUpdater(Counter.class, "value");
    
    private static final AtomicIntegerFieldUpdater<Counter> countUpdater =
        AtomicIntegerFieldUpdater.newUpdater(Counter.class, "count");
    
    public void incrementCounter(Counter c) {
        valueUpdater.addAndGet(c, 1);
        countUpdater.incrementAndGet(c);
    }
    
    // Эффективнее чем обёртка в AtomicLong
    // Если нужно сотни таких полей
}

8. LongAdder (Java 8+)

Оптимизирован для высокой конкуренции (много потоков):

import java.util.concurrent.atomic.LongAdder;

public class LongAdderExample {
    // Используй LongAdder если много потоков инкрементируют одновременно
    private final LongAdder counter = new LongAdder();
    
    public void increment() {
        counter.increment();
    }
    
    public long getTotal() {
        return counter.sum();
    }
    
    public static void main(String[] args) throws InterruptedException {
        LongAdderExample example = new LongAdderExample();
        
        // 100 потоков инкрементируют счетчик
        Thread[] threads = new Thread[100];
        long start = System.nanoTime();
        
        for (int i = 0; i < 100; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 100000; j++) {
                    example.increment();
                }
            });
            threads[i].start();
        }
        
        for (Thread t : threads) {
            t.join();
        }
        
        long elapsed = System.nanoTime() - start;
        System.out.println("Total: " + example.getTotal()); // 10000000
        System.out.println("Time: " + elapsed + "ns");
        
        // LongAdder намного быстрее AtomicLong с высокой конкуренцией
        // AtomicLong: может быть 10x медленнее
        // LongAdder: оптимизирован для этого случая
    }
}

9. DoubleAdder и LongAccumulator

Для более сложных операций:

import java.util.concurrent.atomic.DoubleAdder;
import java.util.concurrent.atomic.LongAccumulator;
import java.util.function.LongBinaryOperator;

public class AdderAccumulatorExample {
    // Для double значений
    private final DoubleAdder total = new DoubleAdder();
    
    public void addAmount(double amount) {
        total.add(amount);
    }
    
    public double getTotal() {
        return total.sum();
    }
    
    // Для произвольной операции (не только сложение)
    // Например: максимум
    private final LongAccumulator maxValue = 
        new LongAccumulator(Math::max, Long.MIN_VALUE);
    
    public void updateMax(long value) {
        maxValue.accumulate(value);
    }
    
    public long getMax() {
        return maxValue.get();
    }
    
    // Для произвольной операции: умножение
    private final LongAccumulator product =
        new LongAccumulator((a, b) -> a * b, 1);
    
    public void multiplyValue(long value) {
        product.accumulate(value);
    }
}

10. CAS (Compare-and-Swap) операция

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

public class CASExample {
    /**
     * Принцип работы CAS:
     * 
     * if (текущее значение == ожидаемое) {
     *     установить новое значение
     *     return true
     * } else {
     *     return false
     * }
     * 
     * Это делается АТОМАРНО на уровне процессора
     */
    
    private int value = 0;
    
    // Пример реализации счетчика через CAS (псевдокод)
    public void incrementViaExplicitCAS() {
        while (true) {
            int current = value;  // Читаем текущее значение
            int next = current + 1;  // Вычисляем новое
            
            // Пытаемся атомарно обновить
            if (compareAndSet(current, next)) {
                return; // Успех
            }
            // Если не получилось, другой поток изменил значение
            // Повторяем (retry/spin)
        }
    }
    
    // Объяснение AtomicInteger.incrementAndGet():
    public int incrementAndGetExplanation() {
        // Внутри используется while(true) с CAS
        while (true) {
            int current = get();
            int next = current + 1;
            
            if (compareAndSet(current, next)) {
                return next;
            }
        }
    }
    
    private boolean compareAndSet(int expected, int newValue) {
        return true; // Упрощённо
    }
    
    private int get() {
        return value; // Упрощённо
    }
}

Сравнение методов синхронизации

public class SynchronizationComparison {
    // 1. synchronized — простой но медленный
    private int syncCounter = 0;
    public synchronized void incrementSync() { syncCounter++; }
    
    // 2. AtomicInteger — быстро для низкой конкуренции
    private AtomicInteger atomicCounter = new AtomicInteger();
    public void incrementAtomic() { atomicCounter.incrementAndGet(); }
    
    // 3. LongAdder — самый быстрый для высокой конкуренции
    private LongAdder adderCounter = new LongAdder();
    public void incrementAdder() { adderCounter.increment(); }
    
    /*
    Производительность при 100 потоках, каждый инкрементирует 100k раз:
    
    synchronized:     ~2000 ms  (очень медленно, много contention)
    AtomicInteger:    ~500 ms   (лучше, но всё ещё медленнее)
    LongAdder:        ~50 ms    (быстро, оптимизирован для этого)
    
    При 1 потоке:
    synchronized:     ~100 ms
    AtomicInteger:    ~50 ms   (быстрее)
    LongAdder:        ~50 ms   (примерно одинаково)
    */
}

Когда использовать что

public class WhenToUseWhat {
    // Простой счетчик, несколько потоков
    private final AtomicInteger requestCount = new AtomicInteger();
    
    // Флаг состояния
    private final AtomicBoolean isRunning = new AtomicBoolean(true);
    
    // Много потоков пишут в счетчик
    private final LongAdder totalRequests = new LongAdder();
    
    // Ссылка на конфигурацию, обновляется редко
    private final AtomicReference<Config> config = 
        new AtomicReference<>(new Config());
    
    // Если нужно обновлять поле в существующем классе
    // Используй AtomicIntegerFieldUpdater, AtomicReferenceFieldUpdater
}

Лучшие практики

  1. Используй AtomicInteger/Long для простых счетчиков

    • Меньше overhead чем synchronized
    • Более читаемо
  2. Используй LongAdder если высокая конкуренция

    • Специально оптимизирован для этого
    • Нет retries как в CAS loop
  3. AtomicReference только для неизменяемых объектов

    • Если хочешь обновлять mutable объект, используй Lock
  4. Комбинируй с volatile если нужна видимость

    • volatile гарантирует видимость
    • Atomic гарантирует атомарность операций
  5. CAS операции — lock-free programming

    • Нет deadlock'ов
    • Нет контекстных переключений потоков
    • Но может быть spinning (CPU intensive)

Главное: Атомарные операции — это основа efficient многопоточного программирования без явных блокировок.