AtomicMarkableReference 简介
1. 概述
在本教程中,我们将深入了解java.util.concurrent.atomic包中的AtomicMarkableReference类**的详细信息。
接下来,我们将介绍该类的 API 方法,并了解如何在实践中使用AtomicMarkableReference类。
2. 目的
AtomicMarkableReference是一个通用类,它封装了对Object的引用和boolean标志。这两个字段耦合在一起,可以一起或单独进行原子 更新。
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的用处,让我们从创建一个Employee POJO 开始:
class Employee {
private int id;
private String name;
// constructor & getters & setters
}
现在,我们可以创建AtomicMarkableReference类的实例:
AtomicMarkableReference<Employee> employeeNode
= new AtomicMarkableReference<>(new Employee(123, "Mike"), true);
对于我们的示例,假设我们的AtomicMarkableReference实例表示组织结构图中的一个节点。它包含两个变量:对Employee类实例的reference和一个指示员工是活跃还是离开公司的mark。
AtomicMarkableReference带有几种方法来更新或检索一个或两个字段。让我们一一看看这些方法:
4.1. getReference()
我们使用getReference方法返回reference变量的当前值:
Employee employee = new Employee(123, "Mike");
AtomicMarkableReference<Employee> employeeNode = new AtomicMarkableReference<>(employee, true);
Assertions.assertEquals(employee, employeeNode.getReference());
4.2. isMarked()
要获取mark变量的值,我们应该调用isMarked方法:
Employee employee = new Employee(123, "Mike");
AtomicMarkableReference<Employee> employeeNode = new AtomicMarkableReference<>(employee, true);
Assertions.assertTrue(employeeNode.isMarked());
4.3. get()
接下来,当我们想要同时检索当前reference和当前mark时,我们使用get方法。要获得mark,我们应该发送一个大小至少为 1的boolean数组作为参数,它将在索引 0 处存储boolean变量的当前值。同时,该方法将返回引用的当前值:
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]);
这种获取reference和mark字段的方式有点奇怪,因为内部Pair类没有暴露给调用者。
Java在其公共 API中没有通用的*Pair<*T, U>类。主要原因是我们可能会过度使用它而不是创建不同的类型。
4.4. put()
如果我们想无条件地更新reference和mark字段,我们应该使用set方法。如果作为参数发送的至少一个值不同,则reference和mark将被更新:
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()
接下来,如果当前reference等于预期reference,并且当前mark等于预期mark,则compareAndSet方法将reference和mark都更新为给定的更新值。**
现在,让我们看看如何使用compareAndSet更新reference和mark字段:
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. weakCompareAndSet()
weakCompareAndSet方法应该是compareAndSet方法的弱版本。**也就是说,它不像 compareAndSet那样提供强大的内存排序保证。此外,在硬件级别获得独占访问可能会虚假失败。
这是 weakCompareAndSet方法的规范。然而,**目前,weakCompareAndSet只是 在底层调用compareAndSet方法 。**因此,它们具有相同的强大实现。
即使这两种方法现在具有相同的实现,我们也应该根据它们的规范使用它们。因此,我们应该将 weakCompareAndSet 视为一个弱原子。
在某些平台和某些情况下,弱原子可能会更便宜。例如,如果我们要在循环中执行 compareAndSet ,使用较弱的版本可能是一个更好的主意。在这种情况下,我们最终会在循环中更新状态,因此虚假失败不会影响程序的正确性。
底线是,弱原子在某些特定用例中可能很有用,因此并不适用于所有可能的场景。因此,当有疑问时,更喜欢更强的compareAndSet。
4.7. attemptMark()
最后,我们有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());
重要的是要注意,即使预期和当前reference值相等,这种方法也可能会虚假失败。因此,我们应该注意方法执行返回的boolean。
如果mark更新成功,则结果为true,否则为false。但是,当当前reference等于预期reference时重复调用会修改mark值。因此,建议在while循环结构中使用此方法。
此失败可能是由于attemptMark方法用于更新字段的基础比较和交换 (CAS) 算法的结果。如果我们有多个线程尝试使用 CAS 更新相同的值,其中一个会设法更改值,而其他线程会收到更新失败的通知。