AtomicStampedReference 原子标记引用
" 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变量上声明的类型的引用。
这是第一个无类型的AtomicStampedReference
getReference()示例:
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. }