Что такое атомарность в java?
Понимаю, что такое атомарные операции - это неделимые операции, которые могут использоваться в многопоточном режиме без синхронизации.
Например метод compareAndSet из java.util.concurrent.atomic пакета.
Но не понятно, что такое атомарность? Если атомарность - это неделимость, то приведите пример пожалуйста, чтобы стало понятно данное понятие в Java.
Ответы (1 шт):
Необходимость координации межпроцессного взаимодействия возникает из-за того, что некоторая область памяти является общей.
Мы также знаем, что все потоки процесса используют одни и те же данные:
Простой пример общей переменной x управляют два потока: A и B:
Thread A
A1. x = 5
A2. print x
Thread B
B1. x = 7
Это недетерминированный код.
Здесь нет механизма координации, поэтому мы не можем сказать, в каком порядке будут происходить эти заявления.
Некоторые возможные результаты:
5 распечатывается и является окончательным значением;
7 распечатывается и является окончательным значением; или же
5 распечатывается, а 7 является окончательным значением.
Обратите внимание, что мы не можем распечатать 7 и получить окончательное значение 5.
Нам даже не нужно иметь несколько потоков, чтобы иметь проблему с параллелизмом. Достаточно иметь прерывания в системе. Рассмотрим приложение, которое используется для подсчета наступления какого-либо события.
Мы будем хранить количество в переменной count.
Мы предоставим пользователю возможность сброса счетчика (кнопка сброса).
Каждый раз, когда мы обнаруживаем событие, мы увеличиваем count с оператором count++;, который выглядит как один отдельный оператор.
count++; разбит на ряд более мелких операций. Если count равно 4, и мы увеличиваем его:
- Читаем текущее значение count (read 4)
- Добавляем 1 (теперь 5)
- Изменяем значение в памяти (write 5)
Теперь представьте, что прерывание происходит в самый неподходящий момент. Прерывание генерируется кнопкой сброса: предполагается, что значение счетчика установлено в ноль.
- Читаем текущее значение count (read 4)
- Добавляем 1 (теперь 5)
- INTERRUPT (обработчик прерывания)
- Изменяем значение в памяти (write 0)
- END INTERRUPT (возврат управления)
- Изменяем значение в памяти (write 5)
Переменная count равна 5, но она должна быть 0 (или 1). Пользователь нажал кнопку сброса, но счет не был сброшен!
Если прерывание сброса произошло до чтения переменной -> 1.
Если прерывание сброса произошло после записи переменной -> 0.
Действие сброса «потеряно».
Эта проблема возникает из-за того, что инструкция count++ на самом деле состоит из трех вещей (чтение, добавление, запись) и может быть прервана в любой момент.
Когда мы выполняем операцию, которая не может быть прервана, мы говорим, что она atomic: атомарная.
Т.е. мы хотим для определенных операций жестко установить последовательность их выполнения = атомарность.
Наиболее часто используемые атомные классы переменных в Java - AtomicInteger, AtomicLong, AtomicBoolean и AtomicReference. Эти классы представляют собой int, long, boolean и объектную ссылку соответственно, которые могут быть атомически обновлены. Основными методами, открываемыми этими классами, являются:
- get() - получает значение из памяти, чтобы были видны изменения, внесенные другими потоками; эквивалентно чтению переменной volatile
- set() - записывает значение в память так, чтобы изменение было видно другим потокам; эквивалентно записи переменной volatile
- lazySet() - в конце концов записывает значение в память, может быть переупорядочено с последующими соответствующими операциями с памятью. Один из вариантов использования - обнуление ссылок, ради сбора мусора, который больше никогда не будет доступен.
- compareAndSet() - возвращает true при успешном выполнении, в противном случае false
- weakCompareAndSet() - Атомные классы также поддерживают метод weakCompareAndSet, который имеет ограниченную применимость. Атомно устанавливает значение для данного обновленного значения, если текущее значение совпадает с ожидаемым значением.
Потоково безопасный пример со счетчиком AtomicInteger:
public class SafeCounterWithoutLock {
private final AtomicInteger counter = new AtomicInteger(0);
public int getValue() {
return counter.get();
}
public void increment() {
while(true) {
int existingValue = getValue();
int newValue = existingValue + 1;
if(counter.compareAndSet(existingValue, newValue)) {
return;
}
}
}
}