Какие знаешь интерфейсы, гарантирующие атомарность?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Интерфейсы и классы, гарантирующие атомарность
Атомарные операции — критичны для потокобезопасности без явных блокировок (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
}
Лучшие практики
-
Используй AtomicInteger/Long для простых счетчиков
- Меньше overhead чем synchronized
- Более читаемо
-
Используй LongAdder если высокая конкуренция
- Специально оптимизирован для этого
- Нет retries как в CAS loop
-
AtomicReference только для неизменяемых объектов
- Если хочешь обновлять mutable объект, используй Lock
-
Комбинируй с volatile если нужна видимость
- volatile гарантирует видимость
- Atomic гарантирует атомарность операций
-
CAS операции — lock-free programming
- Нет deadlock'ов
- Нет контекстных переключений потоков
- Но может быть spinning (CPU intensive)
Главное: Атомарные операции — это основа efficient многопоточного программирования без явных блокировок.