听公开课记下笔记
1、现象
CAS是指 Compare And Swap,比较并交换,是一种重要的同步思想,如果主内存的值和期望值一样,那么久进行修改,否则会一直重试,直到一致为止;
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(5);
System.out.println(atomicInteger.compareAndSet(5,2019)+"\t current data: "+atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(5,1024)+"\t current data: "+atomicInteger.get());
}
}
第一次修改,期望值为5,主内存也为5,修改成功,为2019。第二次修改,期望值为5,主内存为2019,修改失败。
查看AtomicInteger.getAndIncrement()
方法,发现其没有加synchronized
也实现了同步。这是为什么?
2、CAS底层原理
AtomicInteger
内部维护了volatile int value
和private static final Unsafe unsafe
两个比较重要的参数。
查看源码:
//构造函数
private volatile int value;
/**
* Creates a new AtomicInteger with the given initial value.
*
* @param initialValue the initial value
*/
public AtomicInteger(int initialValue) {
value = initialValue;
}
public final int getAndIncrement(){
return unsafe.getAndAddInt(this,valueOffset,1);
}
AtomicInteger.getAndIncrement()
调用了Unsafe.getAndAddInt()
方法。Unsafe
类的大部分方法都是native
的,用来像C语言一样从底层操作内存。
//Var1 是传入的对象值。var2是偏移量
//Var4 是增加的值或者是进行的操作
//var5 依据var1 和Var2获得的内存中的快照值
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
//依据对象和偏移量获取内存中该对象的快照值,一直等到内存中的值与快照值相等位置;
这个方法的var1和var2,就是根据对象和偏移量得到在主内存的快照值var5。然后compareAndSwapInt
方法通过var1和var2得到当前主内存的实际值。如果这个实际值跟快照值相等,那么就更新主内存的值为var5+var4。如果不等,那么就一直循环,一直获取快照,一直对比,直到实际值和快照值相等为止。
比如有A、B两个线程,一开始都从主内存中拷贝了原值为3,A线程执行到var5=this.getIntVolatile
,即var5=3。此时A线程挂起,B修改原值为4,B线程执行完毕,由于加了volatile,所以这个修改是立即可见的。A线程被唤醒,执行this.compareAndSwapInt()
方法,发现这个时候主内存的值不等于快照值3,所以继续循环,重新从主内存获取。
3、CAS缺点:
CAS就是一种自旋锁
1、一直循环下去,内存的开销比较大
2、有ABA的问题
3、只能保证一个变量的原子性,多个变量依然要加锁;
4、ABA的问题
所谓ABA问题,就是比较并交换的循环,存在一个时间差,而这个时间差可能带来意想不到的问题。比如线程T1将值从A改为B,然后又从B改为A。线程T2看到的就是A,但是却不知道这个A发生了更改。尽管线程T2 CAS操作成功,但不代表就没有问题。
有的需求,比如CAS,只注重头和尾,只要首尾一致就接受。但是有的需求,还看重过程,中间不能发生任何修改,这就引出了AtomicReference
原子引用。
public class ABADemo {
static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
public static void main(String[] args) {
System.out.println("-------------ABA问题的产生-----------");
new Thread(()->{
atomicReference.compareAndSet(100, 101);
atomicReference.compareAndSet(101, 100);
},"AAA").start();
new Thread(()->{
try{
TimeUnit.SECONDS.sleep(1);
}catch (Exception e){
e.printStackTrace();
}
System.out.println(atomicReference.compareAndSet(100, 2020)+"\t"+atomicReference.get().toString());
},"BBB").start();
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
}
}
5、解决方案
AtomicReference
AtomicInteger
对整数进行原子操作,如果是一个POJO呢?可以用AtomicReference
来包装这个POJO,使其操作原子化。
User user1 = new User("Jack",25);
User user2 = new User("Lucy",21);
AtomicReference<User> atomicReference = new AtomicReference<>();
atomicReference.set(user1);
System.out.println(atomicReference.compareAndSet(user1,user2)); // true
System.out.println(atomicReference.compareAndSet(user1,user2)); //false
AtomicStampedReference
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100,1);
private static void sloveABA() {
System.out.println("---------ABA问题的解决-----------");
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t第一次版本号: " + stamp);
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
atomicStampedReference.compareAndSet(100,101,
atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName() + "\t第二次版本号: " + atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101,100,
atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName() + "\t第三次版本号: " + atomicStampedReference.getStamp());
}, "CCC").start();
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t第一次版本号: " + stamp);
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
boolean result=atomicStampedReference.compareAndSet(100,2019,
stamp,stamp+1);
System.out.println(Thread.currentThread().getName()+"\t修改成功与否:"+result+" 当前最新版本号"+atomicStampedReference.getStamp());
System.out.println(Thread.currentThread().getName()+"\t当前实际值:"+atomicStampedReference.getReference());
}, "DDD").start();
}
//源码
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp)