Перейти к основному содержимому

Руководство по AtomicMarkableReference

· 6 мин. чтения

1. Обзор

В этом руководстве мы подробно рассмотрим класс AtomicMarkableReference из пакета java.util.concurrent.atomic .

Далее мы рассмотрим методы API класса и увидим, как мы можем использовать класс AtomicMarkableReference на практике.

2. Цель

AtomicMarkableReference — это универсальный класс, который инкапсулирует как ссылку на объект , так и логический флаг. Эти два поля связаны друг с другом и могут обновляться атомарно , либо вместе, либо по отдельности .

AtomicMarkableReference также может быть возможным решением проблемы ABA .

3. Реализация

Давайте более подробно рассмотрим реализацию класса AtomicMarkableReference :

public class AtomicMarkableReference<V> {

private static class Pair<T> {
final T reference;
final boolean mark;
private Pair(T reference, boolean mark) {
this.reference = reference;
this.mark = mark;
}
static <T> Pair<T> of(T reference, boolean mark) {
return new Pair<T>(reference, mark);
}
}

private volatile Pair<V> pair;

// ...
}

Обратите внимание, что AtomicMarkableReference имеет статический вложенный класс Pair , который содержит ссылку и флаг.

Также мы видим, что обе переменные final . В результате всякий раз, когда мы хотим изменить эти переменные, создается новый экземпляр класса Pair , а старый экземпляр заменяется .

4. Методы

Прежде всего, чтобы узнать о полезности AtomicMarkableReference , давайте начнем с создания POJO Employee :

class Employee {
private int id;
private String name;

// constructor & getters & setters
}

Теперь мы можем создать экземпляр класса AtomicMarkableReference :

AtomicMarkableReference<Employee> employeeNode 
= new AtomicMarkableReference<>(new Employee(123, "Mike"), true);

Для наших примеров предположим, что наш экземпляр AtomicMarkableReference представляет узел в организационной диаграмме. Он содержит две переменные: ссылку на экземпляр класса Employee и отметку , указывающую, является ли сотрудник активным или покинул компанию.

AtomicMarkableReference поставляется с несколькими методами для обновления или извлечения одного или обоих полей. Давайте рассмотрим эти методы один за другим:

4.1. получить ссылку()

Мы используем метод getReference для возврата текущего значения ссылочной переменной:

Employee employee = new Employee(123, "Mike");
AtomicMarkableReference<Employee> employeeNode = new AtomicMarkableReference<>(employee, true);

Assertions.assertEquals(employee, employeeNode.getReference());

4.2. помечено()

Чтобы получить значение переменной mark , мы должны вызвать метод isMarked :

Employee employee = new Employee(123, "Mike");
AtomicMarkableReference<Employee> employeeNode = new AtomicMarkableReference<>(employee, true);

Assertions.assertTrue(employeeNode.isMarked());

4.3. получить()

Затем мы используем метод get , когда хотим получить и текущую ссылку , и текущую метку . Для получения отметки мы должны передать в качестве параметра булев массив размером не менее единицы, в котором по индексу 0 будет храниться текущее значение булевой переменной . При этом метод вернет текущее значение ссылки :

Employee employee = new Employee(123, "Mike");
AtomicMarkableReference<Employee> employeeNode = new AtomicMarkableReference<>(employee, true);

boolean[] markHolder = new boolean[1];
Employee currentEmployee = employeeNode.get(markHolder);

Assertions.assertEquals(employee, currentEmployee);
Assertions.assertTrue(markHolder[0]);

Такой способ получения поля ссылки и метки немного странный, потому что внутренний класс Pair не доступен вызывающей стороне.

Java не имеет универсального класса Pair<T, U> в общедоступном API. Основная причина этого заключается в том, что у нас может возникнуть соблазн злоупотребить им вместо создания отдельных типов.

4.4. установлен()

Если мы хотим безоговорочно обновить поля ссылки и метки , мы должны использовать метод set . Если хотя бы одно из значений, отправленных в качестве параметра, отличается, ссылка и метка будут обновлены:

Employee employee = new Employee(123, "Mike");
AtomicMarkableReference<Employee> employeeNode = 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 :

Employee employee = new Employee(123, "Mike");
AtomicMarkableReference<Employee> employeeNode = 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. слабыйCompareAndSet()

Метод weakCompareAndSet должен быть более слабой версией метода compareAndSet . То есть он не обеспечивает строгих гарантий упорядочения памяти, как compareAndSet . Кроме того, может возникнуть ложная ошибка при получении монопольного доступа на аппаратном уровне.

Это спецификация метода weakCompareAndSet . Однако в настоящее время weakCompareAndSet просто вызывает метод compareAndSet под капотом . Итак, у них одинаковая сильная реализация.

Несмотря на то, что эти два метода имеют одинаковую реализацию прямо сейчас, мы должны использовать их на основе их спецификаций. Следовательно, weakCompareAndSet следует рассматривать как слабый атомарный `` .

Слабые атомы могут быть менее дорогими на некоторых платформах и в некоторых обстоятельствах. Например, если мы собираемся выполнить compareAndSet в цикле, лучше использовать более слабую версию. В этом случае мы в конечном итоге обновим состояние, поскольку находимся в цикле, поэтому ложные сбои не повлияют на правильность программы.

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

4.7. попыткаОтметить()

Наконец, у нас есть метод tryMark . Он проверяет, равна ли текущая ссылка ожидаемой ссылке , отправленной в качестве параметра. Если они совпадают, он автоматически устанавливает значение метки в заданное обновленное значение:

Employee employee = new Employee(123, "Mike");
AtomicMarkableReference<Employee> employeeNode = new AtomicMarkableReference<>(employee, true);

Assertions.assertTrue(employeeNode.attemptMark(employee, false));
Assertions.assertFalse(employeeNode.isMarked());

Важно отметить, что этот метод может привести к ложному сбою, даже если ожидаемая и текущая ссылки равны. В результате мы должны обратить внимание на логическое значение , возвращаемое выполнением метода .

Результат будет истинным , если метка была успешно обновлена, или ложным в противном случае. Однако повторный вызов, когда текущая ссылка равна ожидаемой ссылке , изменит значение метки . В результате целесообразно использовать этот метод внутри структуры цикла while .

Этот сбой может произойти из-за базового алгоритма сравнения и замены (CAS), используемого методом tryMark для обновления полей. Если у нас есть несколько потоков, которые пытаются обновить одно и то же значение с помощью CAS, одному из них удается изменить значение, а другие уведомляются о том, что обновление не удалось.

5. Вывод

В этом кратком руководстве мы узнали, как реализован класс AtomicMarkableReference . Более того, мы обнаружили, как можно атомарно обновлять его свойства, используя общедоступные методы API класса.

Как всегда, дополнительные примеры и полный исходный код статьи доступны на GitHub .