AtomicStampedReference 原子标记引用

时间:2020-01-09 10:36:25  来源:igfitidea点击:

" AtomicStampedReference"类提供了一个对象引用变量,可以原子地对其进行读写。原子的意思是,多个线程尝试更改相同的" AtomicStampedReference"不会使" AtomicStampedReference"最终处于不一致状态。

" AtomicStampedReference"与" AtomicReference"的不同之处在于," AtomicStampedReference"在内部既保留对象参考又包含图章。可以通过compareAndSet()方法,使用单个原子比较和交换操作来交换参考和戳记。

" AtomicStampedReference"旨在解决A-B-A问题,而仅凭" AtomicReference"是无法解决的。稍后将介绍A-B-A问题。

创建一个AtomicStampedReference

我们可以创建一个" AtomicStampedReference"实例,如下所示:

Object initialRef   = null;
int    initialStamp = 0;

AtomicStampedReference atomicStampedReference =
    new AtomicStampedReference(intialRef, initialStamp);

创建一个类型化的AtomicStampedReference

我们可以使用Java泛型来创建类型化的AtomicStampedReference。这是一个键入的" AtomicStampedReference"示例:

String initialRef   = null;
int    initialStamp = 0;

AtomicStampedReference<String> atomicStampedReference =
    new AtomicStampedReference<String>(
        initialRef, initialStamp
    );

在此示例中创建的Java AtomicStampedReference将仅接受对String实例的引用。如果我们知道将保留在引用中的类型,建议始终使用带有AtomicStampedReference的泛型类型。

获取AtomicStampedReference参考

我们可以使用AtomicStampedReference的getReference()方法获取存储在AtomicStampedReference中的引用。如果我们有未类型化的" AtomicStampedReference",则" getReference()"方法将返回"对象"引用。如果我们输入的是AtomicStampedReference,则getReference()返回对创建时在AtomicStampedReference变量上声明的类型的引用。

这是第一个无类型的AtomicStampedReferencegetReference()示例:

String initialRef = "first text";

AtomicStampedReference atomicStampedReference = 
    (String)
    new AtomicStampedReference(initialRef, 0);

String reference = 
    atomicStampedReference.getReference();

请注意,必须将getReference()返回的引用强制转换为String,因为在未键入AtomicStampedReference时,getReference()返回一个Object引用。

这是一个键入的" AtomicStampedReference"示例:

String initialRef = "first text";

AtomicStampedReference<String> atomicStampedReference =
    new AtomicStampedReference<String>(
        initialRef, 0
    );

String reference = 
    atomicStampedReference.getReference();

注意,不再需要强制转换getReference()返回的引用,因为编译器知道它将返回一个String引用。

获取AtomicStamped参考邮票

" AtomicStampedReference"还包含" getStamp()"方法,该方法可用于获取内部存储的图章。这是一个getStamp()示例:

String initialRef = "first text";

AtomicStampedReference<String> atomicStampedReference =
    new AtomicStampedReference<>(initialRef, 0);

int stamp = atomicStampedReference.getStamp();

原子获取参考和盖章

我们可以使用get()方法在单个原子操作中从" AtomicStampedReference"获取引用和戳记。 get()方法返回引用作为该方法的返回值。邮票被插入到一个int []数组中,该数组作为参数传递给get()方法。这是一个" get()"示例:

String initialRef   = "text";
String initialStamp = 0;

AtomicStampedReference<String> atomicStampedReference =
    new AtomicStampedReference<>(
        initialRef, initialStamp
    );

int[] stampHolder = new int[1];
String ref = atomicStampedReference.get(stampHolder);

System.out.println("ref = " + ref);
System.out.println("stamp = " + stampHolder[0]);

对于某些类型的并发算法,能够以单个原子操作的形式获得参考和标记是很重要的。

设置AtomicStampedReference参考

我们可以使用set()方法设置存储在AtomicStampedReference实例中的引用。在无类型的AtomicStampedReference实例中,set()方法将Object引用作为第一个参数。在类型为AtomicStampedReference的类型中,set()方法采用任何类型的参数作为我们在声明AtomicStampedReference时声明为其类型的参数。

这是一个" AtomicStampedReference`set()"示例:

AtomicStampedReference<String> atomicStampedReference =
     new AtomicStampedReference<>(null, 0);

String newRef = "New object referenced";
int    newStamp = 1;

atomicStampedReference.set(newRef, newStamp);

对于非类型化或者类型化引用,使用set()方法没有什么区别。我们将遇到的唯一真正的区别是,编译器将限制我们可以在类型化的" AtomicStampedReference"上设置的类型。

比较和设置AtomicStampedReference参考

" AtomicStampedReference"类包含一个名为" compareAndSet()"的有用方法。 compareAndSet()方法可以将存储在AtomicStampedReference实例中的引用与期望的引用进行比较,并将存储的图章与期望的戳进行比较,如果两个引用和图章是相同的(不等于equals( ),但与==中的相同),则可以在AtomicStampedReference实例上设置新的引用。

如果compareAndSet()在AtomicStampedReference中设置了新的引用,则compareAndSet()方法将返回true。否则,compareAndSet()返回false。

这是一个AtomicStampedReference``compareAndSet()示例:

String initialRef   = "initial value referenced";
int    initialStamp = 0;

AtomicStampedReference<String> atomicStringReference =
    new AtomicStampedReference<String>(
        initialRef, initialStamp
    );

String newRef   = "new value referenced";
int    newStamp = initialStamp + 1;

boolean exchanged = atomicStringReference
    .compareAndSet(
        initialRef, newRef, 
        initialStamp, newStamp);
System.out.println("exchanged: " + exchanged);  //true

exchanged = atomicStringReference
    .compareAndSet(
        initialRef, "new string", 
        newStamp, newStamp + 1);
System.out.println("exchanged: " + exchanged);  //false

exchanged = atomicStringReference
    .compareAndSet(
        newRef, "new string", 
        initialStamp, newStamp + 1);
System.out.println("exchanged: " + exchanged);  //false

exchanged = atomicStringReference
    .compareAndSet(
        newRef, "new string", 
        newStamp, newStamp + 1);
System.out.println("exchanged: " + exchanged);  //true

这个例子首先创建一个" AtomicStampedReference",然后使用" compareAndSet()"交换参考和图章。

在第一次调用" compareAndSet()"之后,该示例尝试两次交换引用并标记两次,但均未成功。首次将" initialRef"作为预期参考传递,但此时内部存储的参考为" newRef",因此" compareAndSet()"调用失败。第二次将" initialStamp"作为期望的标记传递,但是此时内部存储的标记是" newStamp",因此" compareAndSet()"调用失败。

最终的" compareAndSet()"调用将成功,因为期望的引用是" newRef",期望的标记是" newStamp"。

AtomicStampedReference和A-B-A问题

" AtomicStampedReference"旨在解决A-B-A问题。 A-B-A问题是将引用从指向A更改为B,然后再更改为A时。

当使用比较交换操作原子地更改引用,并确保只有一个线程可以将引用从旧引用更改为新引用时,检测A-B-A情况是不可能的。

非阻塞算法中可能会出现A-B-A问题。非阻塞算法通常使用对受保护数据结构正在进行的修改的引用,以向其他线程发信号通知当前正在进行修改。如果线程1看到没有正在进行的修改(指向" null"的引用),则另一个线程可以提交修改(引用现在为非" null"),完成修改并将引用换回没有线程的" null" 1个检测到它。在我的非阻塞算法教程中,更详细地说明了非阻塞算法中A-B-A问题的确切发生方式。

通过使用" AtomicStampedReference"而不是" AtomicReference",可以检测到A-B-A情况。线程1可以使用get()从原子上复制引用并从AtomicStampedReference中戳出。如果另一个线程将引用从A更改为B,然后又更改回A,则该戳记将发生更改(提供的线程会合理地更新戳记,例如将其递增)。

以下代码显示了如何使用AtomicStampedReference检测A-B-A情况:

int[] stampHolder = new int[1];
Object ref = atomicStampedReference.get(stampHolder);

if(ref == null){
    //prepare optimistic modification
}

//if another thread changes the
//reference and stamp here,
//it can be detected

int[] stampHolder2 = new int[1];
Object ref2 = atomicStampedReference.get(stampHolder);

if(ref == ref2 && stampHolder[0] == stampHolder2[0]){
    //no modification since 
    //optimistic modification was prepared

} else {
    //retry from scratch.
}