* @return 返回未更新前的值
*/
public final int getAndAddInt(Object var1, long var2, int var4) {
//期待值
int var5;
do {
//获取valueOffset对应的value的值,支持volatile load
var5 = this.getIntVolatile(var1, var2);
//如果原子更新失败,则一直重试,直到成功。
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
**我们看到CAS只能原子的更新一个值,如果我们要原子更新多个值,CAS可以做到吗?**答案是可以的。
#### 2.4、AtomicReference
如果要原子地更新多个值,就需要使用**AtomicReference**。其使用的是**compareAndSwapObject**方法。可以将多个值封装到一个对象中,原子地更换对象来实现原子更新多个值。
public class MultiValue { private int value1; private long value2; private Integer value3;
public MultiValue(int value1, long value2, Integer value3) {
this.value1 = value1;
this.value2 = value2;
this.value3 = value3;
}
}
public class AtomicReferenceTest { public static void main(String[] args) { MultiValue multiValue1 = new MultiValue(1, 1, 1); MultiValue multiValue2 = new MultiValue(2, 2, 2); MultiValue multiValue3 = new MultiValue(3, 3, 3); AtomicReference atomicReference = new AtomicReference<>(); //因为构造AtomicReference时,没有使用有参构造函数,所以value默认值是null atomicReference.compareAndSet(null, multiValue1); System.out.println(atomicReference.get()); atomicReference.compareAndSet(multiValue1, multiValue2); System.out.println(atomicReference.get()); atomicReference.compareAndSet(multiValue2, multiValue3); System.out.println(atomicReference.get()); } } //输出结果 //MultiValue{value1=1, value2=1, value3=1} //MultiValue{value1=2, value2=2, value3=2} //MultiValue{value1=3, value2=3, value3=3}
我们再看一看AtomicReference的compareAndSet方法。
注意:**这里的比较都是使用==而非equals方法**。所以最好封装的MultiValue不要提供set方法。
public final boolean compareAndSet(V expect, V update) { return unsafe.compareAndSwapObject(this, valueOffset, expect, update); }
#### 2.5、CAS的ABA问题
**假设你的账户上有100块钱,你要给女票转50块钱。**
**我们使用CAS进行原子更新账户余额。由于某种原因,你第一次点击转账出现错误,你以为没有发起转账请求,这时候你又点击了一次。系统开启了两个线程进行转账操作,第一个线程进行CAS比较,发现你的账户上预期是100块钱,实际也有100块钱,这时候转走了50,需要设置为100 - 50 = 50 元,这时账户余额为50**
**第一个线程操作成功了,第二个线程由于某种原因阻塞住了;这时候,你的家人又给你转了50块钱,并且转账成功。那你账户上现在又是100块钱;**
**太巧了,第二个线程被唤醒了,发现你的账户是100块钱,跟预期的100是相等的,这时候又CAS为50。大兄弟,哭惨了,你算算,正确的场景你要有多少钱?**这就是CAS存在的ABA问题。
public class AtomicIntegerABA {
private static AtomicInteger atomicInteger = new AtomicInteger(100);
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
//线程1
executorService.execute(() -> {
System.out.println(Thread.currentThread().getName() + " - " + atomicInteger.get());
atomicInteger.compareAndSet(100, 50);
System.out.println(Thread.currentThread().getName() + " - " + atomicInteger.get());
});
//线程2
executorService.execute(() -> {
try {
TimeUnit.MILLISECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " - " + atomicInteger.get());
atomicInteger.compareAndSet(50, 100);
System.out.println(Thread.currentThread().getName() + " - " + atomicInteger.get());
});
//线程3
executorService.execute(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " - " + atomicInteger.get());
atomicInteger.compareAndSet(100, 50);
System.out.println(Thread.currentThread().getName() + " - " + atomicInteger.get());
});
executorService.shutdown();
}
} //输出结果 //pool-1-thread-1 - 100 //pool-1-thread-1 - 50 //pool-1-thread-2 - 50 //pool-1-thread-2 - 100 //pool-1-thread-3 - 100 //pool-1-thread-3 - 50
大家心想,靠,这不是坑吗?那还用。。。。。。。。。。。。。。冷静,冷静。你能想到的问题,jdk都能想到。atomic包提供了一个**AtomicStampedReference**
#### 2.6、AtomicStampedReference
看名字是不是跟AtomicReference很像啊,其实就是在AtomicReference上**加上了一个版本号,每次操作都对版本号进行自增,那每次CAS不仅要比较value,还要比较stamp,当且仅当两者都相等,才能够进行更新。**
public AtomicStampedReference(V initialRef, int initialStamp) { pair = Pair.of(initialRef, initialStamp); } //定义了内部静态内部类Pair,将构造函数初始化的值与版本号构造一个Pair对象。 private static class Pair { final T reference; final int stamp; private Pair(T reference, int stamp) { this.reference = reference; this.stamp = stamp; } static Pair of(T reference, int stamp) { return new Pair(reference, stamp); } }
//所以我们之前的value就对应为现在的pair private volatile Pair pair;
让我们来看一看它的CAS方法。
public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) { Pair current = pair; return //只有在旧值与旧版本号都相同的时候才会更新为新值,新版本号 expectedReference == current.reference && expectedStamp == current.stamp && ((newReference == current.reference && newStamp == current.stamp) || casPair(current, Pair.of(newReference, newStamp))); } private boolean casPair(Pair cmp, Pair val) { return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val); }
还是上面转账的例子,我们使用AtomicStampedReference来看看是否解决了呢。
public class AtomicStampedReferenceABA { /** * 初始化账户中有100块钱,版本号对应0 */ private static AtomicStampedReference atomicInteger = new AtomicStampedReference<>(100, 0);
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
int[] result = new int[1];
//线程1
executorService.execute(() -> {
System.out.println(Thread.currentThread().getName() + " - " + atomicInteger.get(result));
//将100更新为50,版本号+1
atomicInteger.compareAndSet(100, 50, 0, 1);
System.out.println(Thread.currentThread().getName() + " - " + atomicInteger.get(result));
});
//线程2
executorService.execute(() -> {
try {
TimeUnit.MILLISECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " - " + atomicInteger.get(result));
//将50更新为100,版本号+1
atomicInteger.compareAndSet(50, 100, 1, 2);
System.out.println(Thread.currentThread().getName() + " - " + atomicInteger.get(result));
});
//线程3
executorService.execute(() -> {
try {
TimeUnit.SECONDS.sleep(1);
最后
由于细节内容实在太多了,为了不影响文章的观赏性,只截出了一部分知识点大致的介绍一下,每个小节点里面都有更细化的内容!
需要这份文档的朋友可以帮忙点个赞,点击下方神秘超链接,就可以免费获取到了,还有小编准备的一份Java进阶学习路线图(Xmind)以及来年金三银四必备的一份《Java面试必备指南》
mg-KT2JnPE3-1630204782148)]
需要这份文档的朋友可以帮忙点个赞,点击下方神秘超链接,就可以免费获取到了,还有小编准备的一份Java进阶学习路线图(Xmind)以及来年金三银四必备的一份《Java面试必备指南》
|