在很多时候原子引用没法知道共享变量是否被更改过,比如说,当前共享变量的值为A,而线程1想更改A的值为C,那么是可以更改成功的,如果在线程1修改A->C之前,有其他的线程对A进行了修改,例如先把A改为B后又把B改为A,那么线程1还是可以更改成功的。
1. ABA示例
public class Test26 {
static AtomicReference<String> reference = new AtomicReference<>("A");
public static void main(String[] args) {
String prev = reference.get();
other(); // 一开始可以先注释,后开启,这样对业务是没啥影响的,但是主线程不知道共享变量被更改
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("change A -> C :" + reference.compareAndSet(prev, "C"));
}
public static void other() {
new Thread(() -> {
System.out.println("change A -> B :" + reference.compareAndSet(reference.get(), "B"));
},"t1").start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
System.out.println("change B -> A :" + reference.compareAndSet(reference.get(), "A"));
},"t2").start();
}
}
运行结果
2. 避免ABA问题
-
AtomicStampedReference
这个原子引用类通过引入一个stamp变量,在线程需要更新值时候,需同时也比较这个变量的值是不是和预期的值一致。
示例:
class Test27 { static AtomicStampedReference<String> reference = new AtomicStampedReference<>("A", 0); public static void main(String[] args) { String prev = reference.getReference(); // 获取主线程对应的值 int stamp = reference.getStamp(); // 获取主线程对应的stamp值,用来标记被改了几次 other(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(stamp); // 可以看出,在cas的时候不仅用到了值的比较,还用到了stamp值的比较 System.out.println("change A -> C :" + reference.compareAndSet(prev, "C", stamp, stamp+1)); } public static void other() { new Thread(() -> { int stamp = reference.getStamp(); System.out.println("change A -> B :" + reference.compareAndSet(reference.getReference(), "B", stamp, stamp + 1)); },"t1").start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { int stamp = reference.getStamp(); System.out.println("change B -> A :" + reference.compareAndSet(reference.getReference(), "A", stamp, stamp + 1)); },"t2").start(); } }
说明:AtomicStampedReference原子引用类,加了个stamp参数,用来标记共享变量的修改次数(如果stamp每次更新加1的话),也就是在内存中不仅存在要更新的引用值,还存在一个stamp的值,只有当前线程中这两个值和内存中的这两个值相同,即可更新为stamp+1 (以上诉代码为例)
运行结果:
-
AtomicMarkableReference
除了
AtomicStampedReference
还有AtomicMarkableReference
这个类,AtomicMarkableReference
这个类是AtomicStampedReference
的简化版,也就是在其中加入了一个标记示例
class Test28 { static AtomicMarkableReference<String> reference = new AtomicMarkableReference<>("A", false); // 标记值初始为false public static void main(String[] args) { String prev = reference.getReference(); // 原来的引用值 boolean marked = reference.isMarked(); // 标记值 other(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(marked); System.out.println("change A -> C :" + reference.compareAndSet(prev, "C", marked, false)); } public static void other() { // t1线程把标记值改为true new Thread(() -> { boolean marked = reference.isMarked(); System.out.println("change A -> B :" + reference.compareAndSet(reference.getReference(), "B", marked, true)); },"t1").start(); // t2线程把标记值改为true ,主内存中最终的值为true new Thread(() -> { boolean marked = reference.isMarked(); System.out.println("change B -> A :" + reference.compareAndSet(reference.getReference(), "A", marked, true)); },"t2").start(); } }
说明:这里和
AtomicStampedReference
差不多,只是多引入了一个参数mark
运行结果:
文章评论