[超级链接:Java并发学习系列-绪论]
[系列概述: Java并发22:Atomic系列-原子类型整体概述与类别划分]
本章主要对带版本戳的原子引用类型进行学习。
带版本戳的原子引用类型主要是为了解决ABA问题而设计的,下面对ABA问题进行简单描述和示例。
ABA问题概述:
上述过程中,虽然看起来变量X仍然为预期A,其实此时的A并不是之前的那个预期A,它是经过A ==> B B ==> A过程之后的新的A。
下面通过一段简短的代码模拟这种ABA过程:
//ABA问题
System.out.println("==========ABA问题:");
AtomicReference<String> reference = new AtomicReference<>("A");
new Thread(() -> {
//获取期望值
String expect = reference.get();
//打印期望值
System.out.println(Thread.currentThread().getName() + "---- expect: " + expect);
try {
//干点别的事情
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
//打印实际值
System.out.println(Thread.currentThread().getName() + "---- actual: " + reference.get());
//进行CAS操作
boolean result = reference.compareAndSet("A", "X");
//打印操作结果
System.out.println(Thread.currentThread().getName() + "---- result: " + result + " ==》 final reference = " + reference.get());
}).start();
new Thread(() -> {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//进行ABA操作
System.out.print(Thread.currentThread().getName() + "---- change: " + reference.get());
reference.compareAndSet("A", "B");
System.out.print(" -- > B");
reference.compareAndSet("B", "A");
System.out.println(" -- > A");
}).start();
运行结果:
==========ABA问题:
Thread-0---- expect: A
Thread-1---- change: A -- > B -- > A
Thread-0---- actual: A
Thread-0---- result: true ==》 final reference = X
为了解决上述的ABA问题,Java提供了两种带版本戳的原子引用类型:
本章主要以AtomicStampedReference作为学习对象。
AtomicStampedReference提供的方法如下:
1.AtomicStampedReference<>(V initialRef, int initialStamp)
2.getReference()与getStamp()
3.set(V newReference, int newStamp)
重新设置引用对象以及版本戳。
4.attemptStamp(V expectedReference, int newStamp)
如果引用对象为期望值,则重新设置新的版本戳。
5.compareAndSet(V expectedReference,V newReference,int expectedStamp,int newStamp)
如果引用对象为期望值,并且版本戳正确,则赋新值并修改版本戳。
6.get(int[] stampHolder)
实例代码:
//AtomicStampedReference的方法汇总:
System.out.println("\n=========AtomicStampedReference的方法汇总:");
//构造方法:AtomicStampedReference<>(V initialRef, int initialStamp)
System.out.println("构造方法:AtomicStampedReference<>(V initialRef, int initialStamp)");
AtomicStampedReference<String> stampedReference = new AtomicStampedReference<>("David", 1);
//getStamp和getReference:获取版本戳和引用对象
System.out.println("\ngetReference():获取引用对象的值----" + stampedReference.getReference());
System.out.println("getStamp():获取引用对象的值的版本戳----" + stampedReference.getStamp());
//set(V newReference, int newStamp):无条件的重设引用和版本戳的值
stampedReference.set("Joke", 0);
System.out.println("\nset(V newReference, int newStamp):无条件的重设引用和版本戳的值---[reference:"
+ stampedReference.getReference() + ",stamp:" + stampedReference.getStamp() + "]");
//attemptStamp(V expectedReference, int newStamp)
stampedReference.attemptStamp("Joke", 11);
System.out.println("\nattemptStamp(V expectedReference, int newStamp):如果引用为期望值,则重设版本戳---[reference:"
+ stampedReference.getReference() + ",stamp:" + stampedReference.getStamp() + "]");
//compareAndSet(V expectedReference,V newReference,int expectedStamp,int newStamp)
System.out.println("\ncompareAndSet(V expectedReference,V newReference,int expectedStamp,int newStamp):" +
"\n如果引用为期望值且版本戳正确,则赋新值并修改版本戳:");
System.out.println("第一次:" + stampedReference.compareAndSet("Joke", "Tom", 11, 12));
System.out.println("第二次:" + stampedReference.compareAndSet("Tom", "Grey", 11, 12));
System.out.println("weakCompareAndSet不再赘述");
//get(int[] stampHolder):通过版本戳获取引用当前值
//参数为数组类型是因为基本类型无法传递引用,需要使用数组类型
int[] stampHolder = new int[10];
String aRef = stampedReference.get(stampHolder);
System.out.println("\nget(int[] stampHolder):获取引用和版本戳,stampHolder[0]持有版本戳---[reference=" + aRef + ",stamp=" + stampHolder[0] + "].");
运行结果:
=========AtomicStampedReference的方法汇总:
构造方法:AtomicStampedReference<>(V initialRef, int initialStamp)
getReference():获取引用对象的值----David
getStamp():获取引用对象的值的版本戳----1
set(V newReference, int newStamp):无条件的重设引用和版本戳的值---[reference:Joke,stamp:0]
attemptStamp(V expectedReference, int newStamp):如果引用为期望值,则重设版本戳---[reference:Joke,stamp:11]
compareAndSet(V expectedReference,V newReference,int expectedStamp,int newStamp):
如果引用为期望值且版本戳正确,则赋新值并修改版本戳:
第一次:true
第二次:false
weakCompareAndSet不再赘述
get(int[] stampHolder):获取引用和版本戳,stampHolder[0]持有版本戳---[reference=Tom,stamp=12].
通过上面的学习,基本掌握了AtomicStampedReference提供的方法。
下面通过一个简单的实例模拟解决ABA问题:
//通过版本戳解决ABA问题
System.out.println("\n==========通过版本戳解决ABA问题:");
AtomicStampedReference<String> stampedRef = new AtomicStampedReference<>("A", 1);
new Thread(() -> {
//获取期望值
String expect = stampedRef.getReference();
//获取期望版本戳
Integer stamp = stampedRef.getStamp();
//打印期望值和期望版本戳
System.out.println(Thread.currentThread().getName() + "---- expect: " + expect + "-" + stamp);
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
//打印实际值和实际版本戳
System.out.println(Thread.currentThread().getName() + "---- actual: " + stampedRef.getReference() + "-" + stampedRef.getStamp());
//进行CAS操作(带版本戳)
boolean result = stampedRef.compareAndSet("A", "X", stamp, stamp + 1);
//打印操作结果
System.out.println(Thread.currentThread().getName() + "---- result: " + result + " ==》 final reference = " + stampedRef.getReference() + "-" + stampedRef.getStamp());
}).start();
new Thread(() -> {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
////进行ABA操作(带版本戳)
System.out.print(Thread.currentThread().getName() + "---- change: " + stampedRef.getReference() + "-" + stampedRef.getStamp());
stampedRef.compareAndSet("A", "B", stampedRef.getStamp(), stampedRef.getStamp() + 1);
System.out.print(" -- > B" + "-" + stampedRef.getStamp());
stampedRef.compareAndSet("B", "A", stampedRef.getStamp(), stampedRef.getStamp() + 1);
System.out.println(" -- > A" + "-" + stampedRef.getStamp());
}).start();
运行结果:
==========通过版本戳解决ABA问题:
Thread-2---- expect: A-1
Thread-3---- change: A-1 -- > B-2 -- > A-3
Thread-2---- actual: A-3
Thread-2---- result: false ==》 final reference = A-3
通过分析运行结果,发现带版本戳的原子引用类型确实能够解决ABA问题。
关于AtomicMarkableReference的原理其实是与AtomicStampedReference类似的。
因为其版本戳只是boolean类型,所以导致版本状态只有两个:true或者false。
所以,我更倾向于称呼AtomicMarkableReference为带标记的原子引用类型。
关于AtomicStampedReference的具体用法就不再赘述了,有兴趣的博友可以自行查看源代码进行学习。