CAS的缺陷:
1、循环时间长,开销大。
因为do while循环,当CAS一直失败就会一直循环,直至成功,才会结束。
比如说线程2、3、4存心想针对反应有点慢的线程1,那么线程1这个小可怜只能是逆来顺受了。
2、只能保证一个共享变量的原子问题。
当只有一个共享变量时,我们可以用循环CAS的方法来保证其原子性,但是当有多个共享变量时,循环CAS就无法保证原子性,我们可以通过锁来保证原子性。
3、ABA问题---->狸猫换太子
简单说一下ABA问题。
ABA从这个名字上就可以看出一些端倪,某线程把一个共享变量A变成了B然后又变回了A。具体来说说:(需联系到CAS的思想)
比较并交换,线程要修改共享变量时,需先判断主内存中的变量值是否与自己的工作内存中的值相同,若相同才改变,反之继续判断。
举个栗子:
线程1,和线程2,都是”主内存“公司的员工,“主内存”公司中有一笔资金(即共享变量)用于公司之后的发展。
线程1和线程2都交替拥有的使用和监控权限。这里的交替是指线程的抢占执行。两人每次使用的时候需要先确定资金数目,没有改变才能继续使用,如果改变了需要再次确定。
但是线程2最近看中了一个短期项目,然后他就在自己的工作时间里,挪用公款,然后在项目获利之后,又把空给补上了。
之后线程1上班时,看了看自己知道的公司资金是没有变的,然后对他进行操作。
那么这个线程2是不是就钻了空子了呢。用公司的钱给自己赚钱。不得不说,他真是个小机灵鬼儿。但是这肯定是违反公司的规定的。所以公司就需要对自己内部的数据进行优化。也就是ABA问题的解决了。
公司的新规定,每个人操作资金的时候,都会有版本,也就是说这个资金被操作过几次。这样因为自己的任何一次操作都会被系统记录在案。我把它理解为操作的公开化。这样ABA问题就可以被解决了。
因此,在使用CAS前要考虑清楚“ABA”问题是否会影响程序并发的正确性,如果需要解决ABA问题,改用传统的互斥同步可能会比原子类更高效。
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
public class ABADemo {
static AtomicReference<Integer> atomicReference =new AtomicReference<>(100);
static AtomicStampedReference atomicStampedReference=new AtomicStampedReference(100, 1);
public static void main(String[] args) throws InterruptedException {
System.out.println("======以下是ABA问题的产生========");
new Thread(()->{
atomicReference.compareAndSet(100, 102);
System.out.println(atomicReference.get());
atomicReference.compareAndSet(102, 100);
System.out.println(atomicReference.get());
},"线程1").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicReference.compareAndSet(100, 1202));
System.out.println(atomicReference.get());
},"线程2").start();
TimeUnit.SECONDS.sleep(3);
System.out.println("=============以下是ABA问题的解决===========");
new Thread(()->{
int stamp =atomicStampedReference.getStamp();
//为了让线程4也获得初始的版本号
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicStampedReference.compareAndSet(100, 101, stamp, stamp+1);
System.out.println("第一次操作后版本号为"+atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp()+1);
System.out.println("第二次操作后版本号为"+atomicStampedReference.getStamp());
},"线程3").start();
new Thread(()->{
int stamp =atomicStampedReference.getStamp();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("是否更新成功? "+atomicStampedReference.compareAndSet(100, 1202,stamp,stamp+1));
System.out.println("此时的值为 "+atomicStampedReference.getReference());
System.out.println("此时版本号"+atomicStampedReference.getStamp());
}, "线程4").start();
}
}
运行结果:
======以下是ABA问题的产生========
102
100
true
1202
=============以下是ABA问题的解决===========
第一次操作后版本号为2
第二次操作后版本号为3
是否更新成功? false
此时的值为 100
此时版本号3