1. ABA问题的提出
public class ABA {
static AtomicReference<String> ref = new AtomicReference<>("A");
public static void main(String[] args) throws InterruptedException {
String prev = ref.get();
other();
Thread.sleep(1 * 1000);
System.out.println("主线程成功了吗" + ref.compareAndSet("A", "C"));
}
private static void other(){
new Thread(){
@Override
public void run() {
System.out.println("子线程1成功了吗" + ref.compareAndSet("A", "B"));
}
}.start();
new Thread(){
@Override
public void run() {
System.out.println("子线程2成功了吗" + ref.compareAndSet("B", "A"));
}
}.start();
}
}
上面这个代码可以描述CAS中的ABA问题,在A修改为C的过程中,这个参数可能已经发生过变化了。在上面的代码可以看出这个ref参数在other的两个线程里面被修改了两次。但是主线程仍然成功把这个A变为了C。这个是存在风险的。
2. ABA问题的解决方案
2.1 版本号机制
package org.example.juclearning.learning02;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
public class ABA {
static AtomicStampedReference<String> ref = new AtomicStampedReference<>("A",0);
public static void main(String[] args) throws InterruptedException {
String prev = ref.getReference();
int stamp = ref.getStamp();
other();
Thread.sleep(1 * 1000);
System.out.println("主线程成功了吗" + ref.compareAndSet(prev, "C",stamp,stamp+1));
}
private static void other(){
new Thread(){
@Override
public void run() {
int stamp = ref.getStamp();
System.out.println("子线程1成功了吗" + ref.compareAndSet(ref.getReference(), "B",stamp,stamp+1));
}
}.start();
new Thread(){
@Override
public void run() {
int stamp = ref.getStamp();
System.out.println("子线程1成功了吗" + ref.compareAndSet(ref.getReference(), "A",stamp,stamp+1));
}
}.start();
}
}
在上面的代码中采用了AtomicStampedReference类来解决这个问题,在这个类里main加入类版本号机制。例如在第10行、19行、26行获得当前的版本号。在ref进行CAS操作的时候,不光以prev为参照当前变量是否被修改,同时参考当前对象的版本号是否发生变化。
2.2加入是否修改过标记
package org.example.juclearning.learning02;
import java.util.concurrent.atomic.AtomicMarkableReference;
public class T38 {
public static void main(String[] args) throws InterruptedException {
GarbageBag bag = new GarbageBag("装满了垃圾");
AtomicMarkableReference<GarbageBag> ref = new AtomicMarkableReference<>(bag,true);
GarbageBag prev = ref.getReference();
new Thread(){
@Override
public void run() {
bag.setDesc("变成空垃圾袋");
ref.compareAndSet(bag,bag,true,false);
}
}.start();
Thread.sleep(1 * 1000);
boolean result = ref.compareAndSet(prev, new GarbageBag("新的垃圾袋"), true, false);
System.out.println("换垃圾袋成功了吗" + result);
}
}
class GarbageBag{
String desc;
public GarbageBag(String desc) {
this.desc = desc;
}
@Override
public String toString() {
return "GarbageBag{" +
"desc='" + desc + ''' +
'}';
}
public void setDesc(String desc) {
this.desc = desc;
}
}
类似于版本号标记,观察代码的8、11、18行。