Guide to AtomicMarkableReference – AtomicMarkableReference指南

最后修改: 2020年 3月 23日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll dive into the details of the AtomicMarkableReference class from the java.util.concurrent.atomic package.

在本教程中,我们将深入探讨AtomicMarkableReference类的细节,该类来自java.util.concurrent.atomic

Next, we’ll walk through the API methods of the class, and we’ll see how we can use the AtomicMarkableReference class in practice.

接下来,我们将浏览该类的API方法,并看看我们如何在实践中使用AtomicMarkableReference类。

2. Purpose

2.宗旨

AtomicMarkableReference is a generic class that encapsulates both a reference to an Object and a boolean flag. These two fields are coupled together and can be updated atomically, either together or individually.

AtomicMarkableReference是一个通用类,它同时封装了对Object的引用和一个boolean标志。这两个字段被耦合在一起,可以一起或单独更新atomically

AtomicMarkableReference could also be a possible remedy against the ABA problem

AtomicMarkableReference也可能是针对ABA问题的一个可能的补救措施

3. Implementation

3.实施

Let’s take a more in-depth look at the AtomicMarkableReference class implementation:

让我们更深入地看看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;

    // ...
}

Notice that AtomicMarkableReference has a static nested class Pair that holds the reference and flag.

请注意,AtomicMarkableReference有一个静态嵌套类Pair,持有引用和标志。

Also, we see that both variables are final. As a result, whenever we want to modify these variables, a new instance of the Pair class is created, and the old instance is replaced.

另外,我们看到,这两个变量都是final。因此,无论何时我们想修改这些变量,都会创建一个新的Pair类的实例,而旧的实例被替换掉

4. Methods

4.方法

First of all, to discover AtomicMarkableReference‘s usefulness, let’s start by creating an Employee POJO:

首先,为了发现AtomicMarkableReference的用处,让我们先创建一个Employee POJO。

class Employee {
    private int id;
    private String name;
    
    // constructor & getters & setters
}

Now, we can create an instance of the AtomicMarkableReference class:

现在,我们可以创建一个AtomicMarkableReference类的实例。

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

For our examples, let’s suppose that our AtomicMarkableReference instance represents a node in an organization chart. It is holding the two variables: the reference to an instance of the Employee class and a mark that indicates if the employee is active or has left the company.

对于我们的例子,让我们假设我们的 AtomicMarkableReference 实例代表组织结构图中的一个节点。它持有两个变量:指向Employee类的实例的reference和一个mark,表明该员工是在职还是已经离开公司。

AtomicMarkableReference comes with several methods to update or retrieve either one or both fields. Let’s have a look at these methods one by one:

AtomicMarkableReference带有几个方法来更新或检索一个或两个字段。让我们逐一看看这些方法。

4.1. getReference()

4.1.getReference()

We use the getReference method to return the current value of the reference variable:

我们使用getReference方法来返回reference变量的当前值。

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

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

4.2. isMarked()

4.2. isMarked()

To get the value of the mark variable, we should call the isMarked method:

为了获得mark变量的值,我们应该调用isMarked方法。

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

Assertions.assertTrue(employeeNode.isMarked());

4.3. get()

4.3 get()

Next, we use the get method when we want to retrieve both the current reference and the current mark. To get the mark, we should send as a parameter a boolean array of size at least one, which will store at index 0 the current value of the boolean variable. At the same time, the method will return the current value of the reference:

接下来,当我们想检索当前的reference和当前的mark时,我们使用get方法。为了获得mark我们应该发送一个boolean数组作为参数,其大小至少为1,它将在索引0处存储boolean变量的当前值。同时,该方法将返回reference的当前值。

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]);

This way of getting both the reference and the mark fields is a little odd because the inner Pair class is not exposed to the caller.

这种同时获得referencemark字段的方式有点奇怪,因为内部的Pair类没有暴露给调用者。

Java doesn’t have a generic Pair<T, U> class in its public API. The main reason for this is that we may be tempted to overuse it instead of creating distinct types.

Java的公共API中没有一个通用的Pair<T, U>类。其主要原因是,我们可能会倾向于过度使用它而不是创建不同的类型。

4.4. set()

4.4. set()

In case we want to update both the reference and the mark fields unconditionally, we should use the set method. If at least one of the values sent as a parameter is different, the reference and the mark will be updated:

如果我们想无条件地更新referencemark字段,我们应该使用set方法。如果作为参数发送的值中至少有一个是不同的,referencemark将被更新。

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()

4.5.compareAndSet()

Next, the compareAndSet method updates both the reference and the mark to the given updated values if the current reference is equal to the expected reference, and the current mark is equal to the expected mark.

接下来,compareAndSet方法将referencemark都更新为给定的更新值如果当前reference等于预期的reference,并且当前mark等于预期的mark

Now, let’s see how we can update both reference and mark fields using compareAndSet:

现在,让我们看看如何使用referencemark字段更新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());

Also, when calling the compareAndSet method, we get true if the fields were updated or false if the update failed.

另外,当调用compareAndSet方法时,如果字段被更新,我们得到true,如果更新失败,则得到false

4.6. weakCompareAndSet()

4.6.weakCompareAndSet()

The weakCompareAndSet method should be a weaker version of the compareAndSet method. That is, it doesn’t provide strong memory ordering guarantees just like compareAndSet. Also, it may fail spuriously to get exclusive access at the hardware level.

weakCompareAndSet方法应该是compareAndSet方法的弱化版本。也就是说,它不能像compareAndSet一样提供强大的内存排序保证。另外,它可能会在硬件层面上虚假地失败,无法获得独占访问。

This is the specification for the weakCompareAndSet method. However, currently, the weakCompareAndSet simply calls the compareAndSet method under-the-hoodSo, they have the same strong implementation.

这就是weakCompareAndSet方法的规范。然而,目前,weakCompareAndSet只是调用了compareAndSet方法的后台因此,它们有相同的强实现。

Even though these two methods have the same implementation right now, we should use them based on their specifications. Therefore, we should consider weakCompareAndSet as a weak atomic.

尽管这两个方法现在有相同的实现,但我们应该根据它们的规范来使用它们。因此,我们应该将weakCompareAndSet视为一个弱原子

Weak atomics can be less expensive on some platforms and in some circumstances. For instance, if we’re going to perform a compareAndSet in a loop, it may be a better idea to use the weaker version. In this case, we’ll eventually update the state as we’re in a loop, so spurious failures won’t affect the program’s correctness.

在某些平台和某些情况下,弱原子学的成本会更低。例如,如果我们要在一个循环中执行compareAndSet,使用弱化的版本可能是一个更好的主意。在这种情况下,我们最终会在循环中更新状态,所以虚假的失败不会影响程序的正确性。

The bottom line is, weak atomics can be useful in some specific use-cases and, consequently, aren’t applicable to every possible scenario. So, when in doubt, prefer the stronger compareAndSet.

底线是,弱原子学在某些特定的使用情况下是有用的,因此,它并不适用于所有可能的情况。所以,当有疑问时,请选择更强大的compareAndSet.

4.7. attemptMark()

4.7.attemptMark()

Finally, we have the attemptMark method. It checks whether the current reference is equal to an expected reference sent as a parameter. If they match, it sets the value of the mark atomically to the given updated value:

最后,我们有attemptMark方法。它检查当前的reference是否与作为参数发送的预期reference相等。如果它们匹配,它将标记的值原子化地设置为给定的更新值。

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

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

It’s important to note that this method may fail spuriously even if the expected and current reference are equal. As a result, we should pay attention to the boolean returned by the method execution.

需要注意的是,即使预期的和当前的reference相等,这个方法也可能虚假地失败。因此,我们应该注意方法执行所返回的boolean

The result is true if the mark was updated successfully, or false otherwise. However, repeated invocation when the current reference is equal to the expected reference will modify the mark value. As a result, it is advisable to use this method inside a while loop structure.

如果mark被成功更新,其结果是true,否则是false。然而,当当前的reference等于预期的reference时,重复调用将修改mark值。因此,建议while循环结构内使用此方法

This failure may occur as a result of the underlying compare-and-swap (CAS) algorithm used by the attemptMark method to update the fields. If we have multiple threads that are trying to update the same value using CAS, one of them manages to change the value, and the other ones are notified that the update failed.

这种失败可能是由于attemptMark方法用于更新字段的底层比较和交换(CAS)算法所导致的。如果我们有多个线程试图使用CAS更新同一个值,其中一个线程设法改变了该值,而其他线程则被通知更新失败。

5. Conclusion

5.总结

In this quick guide, we learned how the AtomicMarkableReference class is implemented. Moreover, we discovered how we could update its properties atomically by going through the class’s public API methods.

在这个快速指南中,我们了解了AtomicMarkableReference类是如何实现的。此外,我们发现了如何通过该类的公共API方法来原子化地更新其属性。

As always, more examples and the full source code of the article are available over on GitHub.

一如既往,更多的例子和文章的完整源代码可在GitHub上获得