Автор оригинала: Alexandru Florin Hriscu.
1. Обзор
В этом уроке мы подробно рассмотрим класс AtomicMarkableReference из пакета java.util.concurrent.atomic .
Далее мы рассмотрим методы API класса и посмотрим, как мы можем использовать класс AtomicMarkableReference на практике.
2. Цель
AtomicMarkableReference – это универсальный класс, который инкапсулирует как ссылку на Объект , так и логический флаг. Эти два поля связаны вместе и могут быть обновлены атомарно , либо вместе, либо по отдельности .
AtomicMarkableReference также может быть возможным средством решения проблемы ABA .
3. Осуществление
Давайте более подробно рассмотрим реализацию класса AtomicMarkableReference :
public class AtomicMarkableReference{ private static class Pair { final T reference; final boolean mark; private Pair(T reference, boolean mark) { this.reference = reference; this.mark = mark; } static Pair of(T reference, boolean mark) { return new Pair (reference, mark); } } private volatile Pair pair; // ... }
Обратите внимание, что AtomicMarkableReference имеет статический вложенный класс Pair , который содержит ссылку и флаг.
Кроме того, мы видим, что обе переменные являются окончательными . В результате всякий раз, когда мы хотим изменить эти переменные, создается новый экземпляр класса Pair , а старый экземпляр заменяется .
4. Методы
Прежде всего, чтобы обнаружить AtomicMarkableReference полезность, давайте начнем с создания Сотрудника POJO:
class Employee { private int id; private String name; // constructor & getters & setters }
Теперь мы можем создать экземпляр класса AtomicMarkableReference :
AtomicMarkableReferenceemployeeNode = new AtomicMarkableReference<>(new Employee(123, "Mike"), true);
Для наших примеров предположим, что наш Экземпляр AtomicMarkableReference представляет узел в организационной диаграмме. Он содержит две переменные: ссылку на экземпляр класса Employee и метку , которая указывает, активен ли сотрудник или покинул компанию.
AtomicMarkableReference поставляется с несколькими методами обновления или извлечения одного или обоих полей. Давайте рассмотрим эти методы один за другим:
4.1. getReference()
Мы используем метод getReference для возврата текущего значения переменной reference :
Employee employee = new Employee(123, "Mike"); AtomicMarkableReferenceemployeeNode = new AtomicMarkableReference<>(employee, true); Assertions.assertEquals(employee, employeeNode.getReference());
4.2. помечается()
Чтобы получить значение переменной mark , мы должны вызвать метод is Marked :
Employee employee = new Employee(123, "Mike"); AtomicMarkableReferenceemployeeNode = new AtomicMarkableReference<>(employee, true); Assertions.assertTrue(employeeNode.isMarked());
4.3. получить()
Затем мы используем метод get , когда хотим получить как текущую ссылку , так и текущую метку . Чтобы получить mark , мы должны отправить в качестве параметра boolean массив размером не менее одного, который будет хранить в индексе 0 текущее значение boolean переменной . В то же время метод вернет текущее значение ссылки :
Employee employee = new Employee(123, "Mike"); AtomicMarkableReferenceemployeeNode = new AtomicMarkableReference<>(employee, true); boolean[] markHolder = new boolean[1]; Employee currentEmployee = employeeNode.get(markHolder); Assertions.assertEquals(employee, currentEmployee); Assertions.assertTrue(markHolder[0]);
Этот способ получения полей reference и mark немного странный, потому что внутренний класс Pair не доступен вызывающему.
Java не имеет общего класса Pair U> в своем общедоступном API. Основная причина этого заключается в том, что у нас может возникнуть соблазн злоупотреблять им вместо того, чтобы создавать различные типы. U>
4.4. набор()
В случае, если мы хотим безоговорочно обновить поля reference и mark , мы должны использовать метод set . Если хотя бы одно из значений, отправленных в качестве параметра, отличается, то ссылка и метка будут обновлены:
Employee employee = new Employee(123, "Mike"); AtomicMarkableReferenceemployeeNode = new AtomicMarkableReference<>(employee, true); Employee newEmployee = new Employee(124, "John"); employeeNode.set(newEmployee, false); Assertions.assertEquals(newEmployee, employeeNode.getReference()); Assertions.assertFalse(employeeNode.isMarked());
4.5. compareAndSet()
Затем метод compareAndSet обновляет как ссылку , так и метку до заданных обновленных значений , если текущая ссылка равна ожидаемой ссылке , а текущая метка равна ожидаемой метке .
Теперь давайте посмотрим, как мы можем обновить поля reference и mark с помощью compareAndSet :
Employee employee = new Employee(123, "Mike"); AtomicMarkableReferenceemployeeNode = new AtomicMarkableReference<>(employee, true); Employee newEmployee = new Employee(124, "John"); Assertions.assertTrue(employeeNode.compareAndSet(employee, newEmployee, true, false)); Assertions.assertEquals(newEmployee, employeeNode.getReference()); Assertions.assertFalse(employeeNode.isMarked());
Кроме того, при вызове метода compareAndSet мы получаем true , если поля были обновлены, или false , если обновление не удалось.
4.6. Слабый набор параметров()
Метод weakCompareAndSet должен быть более слабой версией метода compareAndSet . То есть он не обеспечивает строгих гарантий упорядочения памяти, как compareAndSet . Кроме того, он может не получить эксклюзивный доступ на аппаратном уровне.
Это спецификация для метода weakCompareAndSet . Однако в настоящее время weakCompareAndSet просто вызывает метод compareAndSet под капотом . Таким образом, у них одинаковая сильная реализация.
Несмотря на то, что эти два метода имеют одинаковую реализацию прямо сейчас, мы должны использовать их в соответствии с их спецификациями. Поэтому мы должны рассматривать weakCompareAndSet как слабый атомарный .
Слабая атомика может быть дешевле на некоторых платформах и в некоторых обстоятельствах. Например, если мы собираемся выполнить compareAndSet в цикле, возможно, будет лучше использовать более слабую версию. В этом случае мы в конечном итоге обновим состояние, когда мы находимся в цикле, поэтому ложные сбои не повлияют на корректность программы.
Суть в том, что слабые атомы могут быть полезны в некоторых конкретных случаях использования и, следовательно, не применимы ко всем возможным сценариям. Поэтому, когда вы сомневаетесь, предпочитайте более сильный compareAndSet.
4.7. отметка о попытке()
Наконец, у нас есть метод attempt Mark . Он проверяет, соответствует ли текущая ссылка ожидаемой ссылке , отправленной в качестве параметра. Если они совпадают, он устанавливает значение метки атомарно на заданное обновленное значение:
Employee employee = new Employee(123, "Mike"); AtomicMarkableReferenceemployeeNode = new AtomicMarkableReference<>(employee, true); Assertions.assertTrue(employeeNode.attemptMark(employee, false)); Assertions.assertFalse(employeeNode.isMarked());
Важно отметить, что этот метод может привести к внезапному сбою, даже если ожидаемый и текущий reference равны. В результате мы должны обратить внимание на логическое значение , возвращаемое выполнением метода .
Результатом будет true , если метка была успешно обновлена, или false в противном случае. Однако повторный вызов, когда текущая ссылка равна ожидаемой ссылке , изменит значение метки . В результате рекомендуется использовать этот метод внутри структуры while loop|/.
Эта ошибка может возникнуть в результате базового алгоритма сравнения и замены (CAS), используемого методом attempt Mark для обновления полей. Если у нас есть несколько потоков, которые пытаются обновить одно и то же значение с помощью CAS, одному из них удается изменить значение, а другие получают уведомление о том, что обновление не удалось.
5. Заключение
В этом кратком руководстве мы узнали, как реализован класс AtomicMarkableReference|/. Более того, мы обнаружили, как мы можем атомарно обновить его свойства, пройдя через открытые методы API класса.
Как всегда, больше примеров и полный исходный код статьи доступны на GitHub .