compare and swap 比较并交换 实现并发算法时常用的一种技术,它是一条CPU并发原语;
它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的(比如 它的i++是原子的)
包含3个操作数: 内存位置 预期原值 更新值
或者说:位置内存值V 旧的预期值A 要修改的更新值B
当且仅当旧的预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做或者重来
执行CAS操作的时候,将内存位置的值与预期原值比较
如果相匹配,那么处理器会自动将该位置值更新为新值
如果不匹配,处理器不做任何操作,多个线程同时进行CAS操作只有一个会成功
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(5);
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(5,308) + "\t" + atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(5,1010) + "\t" + atomicInteger.get());
}
CAS是JDK提供的非阻塞原子性操作,它通过硬件保证了比较-更新的原子性
CAS的原子性实际是由CPU实现的
为什么volatile无原子性,写丢; 但是atomicInteger可以不加锁,保证原子性
底层: unsafe类 + CAS的思想 + native方法来保证原子操作,从而避免synchronized的高开销,执行效率大为提升
底层源码分析:
难点和核心是这个方法!!!
1.Unsafe是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定的内存数据。Unsafe类存在于sun.misc包中,其内部方法操作可以像C指针一样直接操作内存,因为java中CAS操作的执行依赖于Unsafe类的方法
注意: Unsafe类中所有的方法都是native修饰的,也就是说Unsafe类中所有的方法都是直接调用操作系统底层资源执行相应的任务
2.变量valueOffset,表示该变量在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的
3.变量value用volatile修饰,保证了多线程之间的内存可见性
CAS是一条CPU并发原语,原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致的问题。
理解:getAndAddInt是乐观锁,用CAS代替锁,如果出现竞争,则用自旋的方式等待
CAS的缺点
1.循环时间开销很大
我们可以看到getAndAddInt方法执行的时候,有个do-while
如果CAS失败,会一直进行尝试,如果CAS长时间一直不成功,可能会给CPU带来很大的开销
2.只能保证一个共享变量的原子操作
对多个共享变量操作的时候,循环CAS就无法保证操作的原子性,这个时候可以用锁来保证原子性
3.引出ABA问题 A -> B ->A
CAS算法实现的一个重要的前提是需要取出内存中某时刻的数据并在当下时刻比较替换,那么在这个时间差会在导致数据的变化
比如一个线程t1,从内存位置V中取出A,这时候另一个线程t2也从内存中取出A,并且线程t2进行了一些操作将值变成了B,然后线程t2又将V位置数据变成A,这个时候线程t1进行CAS操作后发现内存中仍然是A,然后线程t1操作成功。
尽管线程t1操作成功,并不代表着这个过程是没有问题的。
如何解决ABA问题
原子引用 :AtomicReference<V>
public class AtomicReferenceDemo {
public static void main(String[] args) {
User z3 = new User("zhangsan", 18);
User li4 = new User("lisi", 20);
AtomicReference<User> atomicReference = new AtomicReference<>();
atomicReference.set(z3);
System.out.println(atomicReference.compareAndSet(z3, li4) + "\t" + atomicReference.get());
System.out.println(atomicReference.compareAndSet(z3, li4) + "\t" + atomicReference.get());
}
}
@Data
@AllArgsConstructor
class User{
private String name;
private int age;
}
时间戳的原子引用
原子引用 + 新增一种机制,修改版本号(类似时间戳)
t1 100 1
t2 100 1 101 2 100 3
最终引出 AtomicStampedReference
ABA问题的解决
使用 AtomicStampedReference
// 以下是ABA问题的解决
new Thread(()->{
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t第1次版本号" + 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第2次版本号:" + atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t第3次版本号:" + atomicStampedReference.getStamp());
},"t3").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, 2022, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName()+"\t修改是否成功" + result);
System.out.println("当前实际最新版本号" + atomicStampedReference.getStamp());
System.out.println("当前实际最新值" + atomicStampedReference.getReference());
},"t4").start();