import org.apache.xmlbeans.impl.xb.xsdschema.Public; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; /** * 1 cas : CompareAndSet * 2 比较并交换 * 3 自旋锁 * 4 Unsafe类 */ public class CasDemo { public static void main(String[] ags){ AtomicInteger atomicInteger = new AtomicInteger(5); //期望值:主物理内存的值,比较并set,比较的主物理内存的值有没有被别的线程改过。如果期望值跟真实值不同,则从主物理内存重新获取。 System.out.println(atomicInteger.compareAndSet(5,2019) + "\t currentData is : " + atomicInteger.get()); System.out.println(atomicInteger.compareAndSet(5,2048) + "\t currentData is : " + atomicInteger.get()); //i++ atomicInteger.getAndIncrement(); //原子引用 User user1 = new User("zhang",11); User user2 = new User("li",23); AtomicReference<User> atomicUser = new AtomicReference<>(user1); System.out.println(atomicUser.compareAndSet(user1,user2) + " \t " + atomicUser.get().toString()); System.out.println(atomicUser.compareAndSet(user1,user2) + " \t " + atomicUser.get().toString()); //ABA问题解决:增加修改机制 版本号。类似时间戳,各个线程的提交后版本号依次增加,ABA,更改后提交,发现版本号号低于主内存的版本号,修改失败。 //解决方式AtomaticStampedReference<V> 时间戳式的原子引用 /* atomicInteger.getAndIncrement()底层代码 this:当前对象; valueOffset:内存偏移量,实际为内存地址; public final int getAndIncrement(){ return unsafe.getAndAddInt(this, valueOffset,1); } unsafe.getAndAddInt();底层代码 public final int getAndAddInt(Object var1, lang var2, int var4){ int var5; do{ var5 = this.getIntVolatile(var1,var2); }while(!this.compareAndSwapInt(var1,var2, var5, var5 +var4)); return var5; */ } /* 1 Unsafe : 是CAS的核心类,由于java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe类存在于sun.misc包中, 其内部方法操作可以像C的指针一样直接操作内存,因为java中CAS操作的执行依赖于Unsafe类的方法。 Unsafe类中所有的方法都是native修饰的,也就是Unsafe类中所有的方法都可以直接调用操作系统底层资源执行相应任务。 2 变量valueOffset,表示该变量值在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的。nsafe.getAndAddInt(this, valueOffset,1); 3 变量value用volatile修饰,保证了多线程之间的内存可见性。 CAS Compare-And-Swap,它是一条CPU并发原语。 cas并发原语体现在java语言中就是sun.misc.Unsafe类中的各种方法。调用Unsafe类中的cas方法,JVM会实现出cas汇编指令。这是一种 完全依赖于硬件的功能,通过它实现了原子操作。由于cas是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成,用于完成某个功能的一个过程, 并且原语的执行必须是连续的,在执行过程中不允许被中断,即cas是一条cpu的原子指令,不会造成所谓的数据不一致问题。 假设线程A/B同时执行getAndAddInt操作(分别跑在不同cpu上); 1 AtomicInteger中原始值为3,即主内存中AtomicInteger的value值为3,根据jmm模型,A和B分别持有一份value为3的副本到各自的工作内存中; 2线程A通过getIntVolatile(var1,var2)拿到value值为3,这时线程A被挂起。 3 线程B也通过getIntVolatile(var1,var2)拿到value值为3,但B没有被挂起,执行compareAndSwapInt方法比较内存值也为3,成功修改内存值为4, 线程B完成,一切OK. 4 这时线程A恢复,执行执行compareAndSwapInt方法比较,发现自己手中的3跟主内存中的4不一致,说明该值已经被其他线程抢先一步修改过了, 则本次修改失败,只能重新读取,读取重新来一遍。 5 线程A重新获得value值,因为value被volatile修饰,所以其他线程对他的修改,线程A总能看到,线程A继续执行compareAndSwapInt进行比较替换, 直到成功。 CAS 比较当前工作内存中的值和主内存中的值,如果相同则执行规定操作,否则继续比较直到主内存和工作内存中的值一致为止。 CAS应用 CAS有3个操作数,内存值V,旧的预期值A, 要修改的更新值B。当且仅当预期值A和内存值V相同时,将内存值修改为B,否则什么也不做。 CAS缺点:1 循环时间长,开销大。do while,如果CAS失败,会一直进行尝试,如果CAS长时间一直不成功,可能会给CPU带来很大开销; 2 只能保证一个共享变量的原子操作,当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但对多个共享变量操作时,循环CAS无法保证操作的原子性,这时候就可以用锁来保证原子性; 3 导致ABA问题(首尾相同,中间会变化),CAS算法实现的一个前提是取出内存中某个时刻的数据并在当下时刻比较并替换,那么在这个时间差内会导致数据的变化。线程1和线程2同时取出内存某地址的值A,线程2对A值转为B后, 写回主内存,又从主内存取出B,装换成A,写回主内存。此时线程1进行CAS操作发现内存中仍然是A,线程1操作成功。尽管线程1操作成功,但不代表这个过程没有问题。 */ } class User{ String userName; int age; User(String userName, int age){ this.age =age; this.userName =userName; } public String toString(){ return "name:" + userName + " age :" + age; } }
cas CompareAndSwap
最新推荐文章于 2022-05-10 11:45:00 发布