目录
一、CAS缺点
1、CAS中的ABA问题
CAS操作想要进行赋值时,会拿原来的取值和现在的值比较;但是万一这个值在我们取值之后,被别的线程改变了,但是紧接着又被别的线程改回来了;比较一下值,发现是相同的,我们认为没人修改这个值,可以进行赋值;但是其实这个值已经被改变过来了,这就是CAS中的ABA问题。
举个银行的例子,如果A账户存100万,B拿了50万,在A发现之前又还了回去,A查看账户还是100万,并不知道自己的钱被拿走过,这当然是存在很大问题的。为了避免这种情况发生,Atomic提供了一个AtomicStampedReference类,在AtomicInteger的基础上增加了一个版本号,每修改一次只需要设置不同的版本号即可。
首先查看产生ABA问题的代码:
- A线程睡眠1秒,初始值为1,调用两次compareAndSet方法,第一次预期值为1,更新值为2;第二次预期值为2,更新值为1;
- B线程睡眠3秒,初始值为1,调用一次compareAndSet方法,预期值为1,更新值为30,通过返回true/false判断B线程操作是否成功,以及最终结果。
代码如下:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;
public class AtomicTest {
static AtomicInteger integer = new AtomicInteger(1);
public static void main(String[] args) {
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
integer.compareAndSet(1, 2);
integer.compareAndSet(2, 1);
}, "t1").start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean b = integer.compareAndSet(1, 30);
System.out.println("操作是否成功: " + b + "最终结果:"
+ integer.get());
}, "t2").start();
//ABA 问题会产生
}
}
运行结果为:
AtomicStampedReference解决ABA问题:
package com.tulun.duoxiancheng;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;
public class AtomicTest {
static AtomicStampedReference<Integer> stampedReference =
new AtomicStampedReference<>(1, 0);
//版本号
public static void main(String[] args) {
new Thread(() -> {
int stamp = stampedReference.getStamp();
System.out.println("-- 线程 " + Thread.currentThread().getName() + "未操作之前的版本号为:" +
stamp);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
stampedReference.compareAndSet(1, 2, stamp,
stamp + 1);
System.out.println("-- 线程 " + Thread.currentThread().getName() + "操作一次之后的版本号为: