AtomicReference与AtomicStampedReference
以下模拟一个情景,一家商铺要做一个活动,对那些账户余额小于10的用户,充值20元礼金,促进消费。
使用AtomicReference来实现这个逻辑的demo:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
public class AtomicReferenceTest {
//设置一个初始值,为6
static AtomicReference<Integer> money = new AtomicReference(6);
public static void main(String[] args) {
//设置一个初始值,为6
money.set(6);
//模拟后台线程,这3个线程的作用就是无限循环找出账户金额小于10的用户,然后对该用户充值20元
for (int i = 0; i < 3; i++) {
new Thread(() -> {
{
while (true) {
while (true) {
Integer m = money.get();
if (m < 10) {
if (money.compareAndSet(m, m + 20)) {
System.out.println("余额小于十元,进行充值,充值后余额=" + money.get() + "元");
break;
}
} else {
break;
}
}
}
}
}).start();
}
//模拟用户消费的线程,消费10次,每次只消费10元,金额不够则给出提示
new Thread(() -> {
for (int i = 0; i < 10; i++) {
while (true) {
Integer m = money.get();
if (m > 10) {
System.out.println("账户余额足够,可以消费");
if (money.compareAndSet(m, m - 10)) {
System.out.println("消费成功,消费后余额=" + money.get() + "元");
break;
}
} else {
System.out.println("余额不足,余额=" + money.get() + "元");
break;
}
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
运行结果如下
可以发现用户账户只要金额小于10,后台线程就会给用户账户充值,如此反复,这中逻辑肯定是不符合商家利益的,这也是CAS方式会出现的ABA问题。
接下来使用AtomicStampedReference来改造以上逻辑,如下:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
public class AtomicStampedReferenceTest {
//设置一个初始值,为6
static AtomicStampedReference<Integer> money = new AtomicStampedReference(6, 0);
public static void main(String[] args) {
//模拟后台线程,这3个线程的作用就是无限循环找出账户金额小于10的用户,然后对该用户充值20元
for (int i = 0; i < 3; i++) {
//取出stamp
final int stamp = money.getStamp();
new Thread(() -> {
{
while (true) {
while (true) {
Integer m = money.getReference();
if (m < 10) {
if (money.compareAndSet(m, m + 20, stamp, stamp + 1)) {
System.out.println("余额小于十元,进行充值,充值后余额=" + money.getReference() + "元");
break;
}
} else {
break;
}
}
}
}
}).start();
}
//模拟用户消费的线程,消费10次,每次只消费10元,金额不够则给出提示
new Thread(() -> {
for (int i = 0; i < 10; i++) {
while (true) {
int stamp = money.getStamp();
Integer m = money.getReference();
if (m > 10) {
System.out.println("账户余额足够,可以消费");
if (money.compareAndSet(m, m - 10, stamp, stamp + 1)) {
System.out.println("消费成功,消费后余额=" + money.getReference() + "元");
break;
}
} else {
System.out.println("余额不足,余额=" + money.getReference() + "元");
break;
}
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
运行结果如下
可以发现只充值了一次,不会再出现反复充值的情况了。
总结
相比起AtomicReference,AtomicStampedReference除了维护了对象值,还维护了一个状态标识位stamp,在更新对象值时,还需要同时更新stamp的值,二者合一才是能否成功修改值的判断规则,可以抽象的想象为,使用了stamp属性形成了一个对象值的修改流水历史,这样即使对象值被修改回原值,逻辑也不会再被运行,因为stamp值肯定是不同的,那么就不匹配规则了,这也是AtomicStampedReference能够实现只充值了一次逻辑的关键所在,这也是对CAS操作的ABA问题的解决思路。