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

Для чего нужна атомарность?

1.0 Junior🔥 131 комментариев
#Основы Java

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

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

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

# Атомарность в Java и базах данных

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

Атомарность в базах данных

В контексте БД атомарность означает, что транзакция либо полностью завершена (committed), либо полностью откачена (rolled back).

Проблемы без атомарности

// Сценарий: перевод денег между счетами
public void transferMoney(Account from, Account to, double amount) {
    // Шаг 1: снимаем деньги со счета
    from.withdraw(amount);  // БД обновлена
    
    // ... что-то пошло не так ...
    throw new RuntimeException("Сервер упал!");
    
    // Шаг 2: никогда не выполнится
    to.deposit(amount);
}

Результат без атомарности: деньги снялись, но не пришли адресату — потеря денег!

Решение: транзакции

@Service
public class AccountService {
    @Autowired
    private AccountRepository accountRepository;
    
    // Гарантирует атомарность
    @Transactional
    public void transferMoney(Long fromId, Long toId, double amount) {
        Account from = accountRepository.findById(fromId).orElseThrow();
        Account to = accountRepository.findById(toId).orElseThrow();
        
        from.setBalance(from.getBalance() - amount);
        to.setBalance(to.getBalance() + amount);
        
        // Если какое-то исключение — вся операция откатится
        accountRepository.save(from);
        accountRepository.save(to);
    }
}

Временная шкала:

NAME: transferMoney(1->2, 100)
START TRANSACTION
  SELECT Account WHERE id=1 (FOR UPDATE)
  UPDATE Account SET balance -= 100 WHERE id=1
  SELECT Account WHERE id=2 (FOR UPDATE)
  UPDATE Account SET balance += 100 WHERE id=2
  COMMIT (либо ROLLBACK если ошибка)

Если возникает исключение на любом этапе:

  • База откатывает ВСЕ изменения
  • Состояние как будто никаких операций не было

Атомарность в многопоточной Java

В многопоточном коде атомарность гарантирует, что операция не будет прервана другим потоком на полпути.

Проблема Race Condition

public class Counter {
    private int count = 0;  // Не атомарная операция
    
    public void increment() {
        count++;  // Это ТРИ операции: read, increment, write
    }
    
    public int getCount() {
        return count;
    }
}

Что происходит с двумя потоками:

Поток 1: READ count=0
Поток 2: READ count=0
Поток 1: INCREMENT 0->1
Поток 2: INCREMENT 0->1
Поток 1: WRITE count=1
Поток 2: WRITE count=1

Результат: count=1 (должен быть 2!)

Решение 1: Synchronized

public class Counter {
    private int count = 0;
    
    public synchronized void increment() {
        count++;
    }
    
    public synchronized int getCount() {
        return count;
    }
}

Теперь операция атомарна:

Поток 1: ACQUIRE LOCK
Поток 1: READ count=0
Поток 1: INCREMENT 0->1
Поток 1: WRITE count=1
Поток 1: RELEASE LOCK
Поток 2: ACQUIRE LOCK
Поток 2: READ count=1
Поток 2: INCREMENT 1->2
Поток 2: WRITE count=2
Поток 2: RELEASE LOCK

Решение 2: Atomic классы (предпочтительно)

import java.util.concurrent.atomic.AtomicInteger;

public class Counter {
    private AtomicInteger count = new AtomicInteger(0);
    
    public void increment() {
        count.incrementAndGet();  // Атомарная операция
    }
    
    public int getCount() {
        return count.get();
    }
}

Это быстрее, чем synchronized, используя CAS (Compare-And-Swap):

// AtomicInteger использует CAS внутри
public final int incrementAndGet() {
    for (;;) {
        int current = get();
        int next = current + 1;
        if (compareAndSet(current, next))  // Атомарная операция на CPU
            return next;
    }
}

Другие Atomic классы

// Для примитивов
AtomicInteger atomicInt = new AtomicInteger(0);
atomicInt.incrementAndGet();      // 1
atomicInt.decrementAndGet();      // 0
atomicInt.addAndGet(5);          // 5
atomicInt.getAndSet(10);         // возвращает 5, устанавливает 10

// Для булевых
AtomicBoolean flag = new AtomicBoolean(false);
flag.set(true);
flag.compareAndSet(true, false); // Атомарное сравнение и обновление

// Для ссылок на объекты
AtomicReference<User> user = new AtomicReference<>();
user.set(new User("John"));
User oldUser = user.getAndSet(new User("Jane"));

// Для массивов
AtomicIntegerArray array = new AtomicIntegerArray(10);
array.incrementAndGet(0);  // Элемент 0 + 1

Практический пример: счетчик запросов

@Service
public class RequestLogger {
    private AtomicInteger requestCount = new AtomicInteger(0);
    private AtomicInteger errorCount = new AtomicInteger(0);
    
    @Around("@annotation(LogRequest)")
    public Object logRequest(ProceedingJoinPoint joinPoint) throws Throwable {
        requestCount.incrementAndGet();  // Атомарно увеличиваем счетчик
        
        try {
            return joinPoint.proceed();
        } catch (Exception e) {
            errorCount.incrementAndGet();  // Атомарно увеличиваем ошибки
            throw e;
        }
    }
    
    public int getTotalRequests() {
        return requestCount.get();
    }
    
    public int getTotalErrors() {
        return errorCount.get();
    }
}

Атомарность в потокобезопасных коллекциях

// ConcurrentHashMap использует атомарные операции
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();

// Атомарная операция: incrementAndGet для значения
map.putIfAbsent("count", 0);
Integer newValue = map.compute("count", (k, v) -> v + 1);  // Атомарно

// CopyOnWriteArrayList
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("item1");  // Атомарная операция

Когда атомарность критична

  1. Финансовые системы: каждая транзакция либо полностью выполняется, либо откатывается
  2. Счетчики и метрики: в многопоточной среде
  3. Кэширование: обновление кэша должно быть атомарным
  4. Распределенные системы: гарантия консистентности данных

Производительность

// Медленно: synchronized блокирует весь объект
public synchronized void incrementSynchronized() {
    count++;
}

// Быстро: только один элемент блокируется
public void incrementAtomic() {
    atomicCount.incrementAndGet();
}

// Ещё быстрее: для несвязанных переменных используй разные Atomic
private AtomicInteger incrementCount = new AtomicInteger(0);
private AtomicInteger decrementCount = new AtomicInteger(0);

Вывод

  1. Атомарность в БД — транзакция либо полностью выполняется, либо откатывается (ACID)
  2. Атомарность в Java — операция не прерывается другим потоком
  3. Atomic классы — предпочтительное решение для многопоточного кода
  4. Без атомарности — race conditions, потеря данных, несогласованность
  5. Цена — синхронизация имеет оверхэд, но необходима для correctness
Для чего нужна атомарность? | PrepBro