Рубрики
Без рубрики

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

Погрузитесь в детали класса AtomicMarkableReference из пакета java.util.concurrent.atomic

Автор оригинала: 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 :

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

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

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

4.1. getReference()

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

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

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

4.2. помечается()

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

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

Assertions.assertTrue(employeeNode.isMarked());

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

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

Employee employee = new Employee(123, "Mike");
AtomicMarkableReference employeeNode = 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");
AtomicMarkableReference 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 обновляет как ссылку , так и метку до заданных обновленных значений , если текущая ссылка равна ожидаемой ссылке , а текущая метка равна ожидаемой метке .

Теперь давайте посмотрим, как мы можем обновить поля reference и mark с помощью compareAndSet :

Employee employee = new Employee(123, "Mike");
AtomicMarkableReference 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. Слабый набор параметров()

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

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

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

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

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

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

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

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