CAS技术之底层原理
一、什么是CAS
CAS的全称为Compare-And-Swap ,它是一条CPU并发原语.
它的功能是判断内存某个位置的值是否为预期值,如果是则更新为新的值,这个过程是原子的.
CAS并发原语提现在Java语言中就是sun.miscUnSafe类中的各个方法.调用UnSafe类中的CAS方法,JVM会帮我实现CAS汇编指令.这是一种完全依赖于硬件 功能,通过它实现了原子操作,再次强调,由于CAS是一种系统原语,原语属于操作系统用于范畴,是由若干条指令组成,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许中断,也即是说CAS是一条原子指令,不会造成所谓的数据不一致的问题.
在Java发展初期,java语言是不能够利用硬件提供的这些便利来提升系统的性能的。而随着java不断的发展,Java本地方法(JNI或JNA)的出现,使得java程序越过JVM直接调用本地方法提供了一种便捷的方式,因而java在并发的手段上也多了起来。而在Doug Lea提供的cucurenct包中,CAS理论是它实现整个java包的基石。
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该 位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前 值。)CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”
通常将 CAS 用于同步的方式是从地址 V 读取值 A,执行多步计算来获得新 值 B,然后使用 CAS 将 V 的值从 A 改为 B。如果 V 处的值尚未同时更改,则 CAS 操作成功。
类似于 CAS 的指令允许算法执行读-修改-写操作,而无需害怕其他线程同时 修改变量,因为如果其他线程修改变量,那么 CAS 会检测它(并失败),算法 可以对该操作重新计算。
代码示例1 -- atomicInteger
[code]/** * Description * @date 2019-04-12 9:57 * 1.什么是CAS ? ===> compareAndSet * 比较并交换 **/ public class CASDemo { public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger(5); System.out.println(atomicInteger.compareAndSet(5, 2019)+"\t current"+atomicInteger.get()); System.out.println(atomicInteger.compareAndSet(5, 2014)+"\t current"+atomicInteger.get()); } }
代码示例2 -- 原子引用
[code]@Getter@Setter@AllArgsConstructor@ToString class User{ private String name; private int age; } public class AtomicReferenceDemo { public static void main(String[] args) { User zs = new User("zs", 22); User ls = new User("ls", 22); AtomicReference<User> userAtomicReference = new AtomicReference<>(); userAtomicReference.set(zs); // 第一次比较交换成功,因为主内存中的值跟期望值一致(zs == zs) System.out.println(userAtomicReference.compareAndSet(zs,ls)+"\t"+userAtomicReference.get().toString()); // 第二次比较交换失败,因为主内存中的值跟期望值不一致(ls != zs) System.out.println(userAtomicReference.compareAndSet(zs,ls)+"\t"+userAtomicReference.get().toString()); } }
sun.miscUnSafe类:
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指令,同时借助JNI来完成Java的非阻塞算法。其它原子操作都是利用类似的特性完成的。而整个J.U.C都是建立在CAS之上的,因此对于synchronized阻塞算法,J.U.C在性能上有了很大的提升。
三、CAS存在的问题 -- 虽然很高效的解决原子操作,但是CAS仍然存在三大问题。
- 1、ABA问题:当你获得对象当前数据后,在准备修改为新值前,对象的值被其他线程连续修改了两次,而经过两次修改后,对象的值又恢复为旧值,这样当前线程无法正确判断这个对象是否修改过
解决办法:JDK1.5可以利用类AtomicStampedReference来解决这个问题,AtomicStampedReference内部不仅维护了对象值,还维护了一个时间戳。当AtomicStampedReference对应的数值被修改时,除了更新数据本身外,还必须要更新时间戳,对象值和时间戳都必须满足期望值,写入才会成功
[code]/** * Description: ABA问题的解决 * * @author veliger@163.com * @date 2019-04-12 21:30 **/ public class ABADemo { private static AtomicReference<Integer> atomicReference=new AtomicReference<>(100); private static AtomicStampedReference<Integer> stampedReference=new AtomicStampedReference<>(100,1); public static void main(String[] args) { System.out.println("===以下是ABA问题的产生==="); new Thread(()->{ atomicReference.compareAndSet(100,101); atomicReference.compareAndSet(101,100); },"t1").start(); new Thread(()->{ //先暂停1秒 保证完成ABA try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(ato 2145f micReference.compareAndSet(100, 2019)+"\t"+atomicReference.get()); },"t2").start(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("===以下是ABA问题的解决==="); new Thread(()->{ int stamp = stampedReference.getStamp(); System.out.println(Thread.currentThread().getName()+"\t 第1次版本号"+stamp+"\t值是"+stampedReference.getReference()); //暂停1秒钟t3线程 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } stampedReference.compareAndSet(100,101,stampedReference.getStamp(),stampedReference.getStamp()+1); System.out.println(Thread.currentThread().getName()+"\t 第2次版本号"+stampedReference.getStamp()+"\t值是"+stampedReference.getReference()); stampedReference.compareAndSet(101,100,stampedReference.getStamp(),stampedReference.getStamp()+1); System.out.println(Thread.currentThread().getName()+"\t 第3次版本号"+stampedReference.getStamp()+"\t值是"+stampedReference.getReference()); },"t3").start(); new Thread(()->{ int stamp = stampedReference.getStamp(); System.out.println(Thread.currentThread().getName()+"\t 第1次版本号"+stamp+"\t值是"+stampedReference.getReference()); //保证线程3完成1次ABA try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } boolean result = stampedReference.compareAndSet(100, 2019, stamp, stamp + 1); System.out.println(Thread.currentThread().getName()+"\t 修改成功否"+result+"\t最新版本号"+stampedReference.getStamp()); System.out.println("最新的值\t"+stampedReference.getReference()); },"t4").start(); }
- 2、循环时间长开销大。自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。
解决办法:JVM支持处理器提供的pause指令,使得效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令,使CPU不会消耗过多的执行资源,第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。
- 3、只能保证一个共享变量的原子操作。当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。
解决办法:从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。
四、concurrent包的实现
由于java的CAS同时具有 volatile 读和volatile写的内存语义,因此Java线程之间的通信现在有了下面四种方式:
A线程写volatile变量,随后B线程读这个volatile变量。
A线程写volatile变量,随后B线程用CAS更新这个volatile变量。
A线程用CAS更新一个volatile变量,随后B线程用CAS更新这个volatile变量。
A线程用CAS更新一个volatile变量,随后B线程读这个volatile变量。
Java的CAS会使用现代处理器上提供的高效机器级别原子指令,这些原子指令以原子方式对内存执行读-改-写操作,这是在多处理器中实现同步的关键(从本质上来说,能够支持原子性读-改-写指令的计算机器,是顺序计算图灵机的异步等价机器,因此任何现代的多处理器都会去支持某种能对内存执行原子性读-改-写操作的原子指令)。同时,volatile变量的读/写和CAS可以实现线程之间的通信。把这些特性整合在一起,就形成了整个concurrent包得以实现的基石。如果我们仔细分析concurrent包的源代码实现,会发现一个通用化的实现模式:
首先,声明共享变量为volatile;
然后,使用CAS的原子条件更新来实现线程之间的同步;
同时,配合以volatile的读/写和CAS所具有的volatile读和写的内存语义来实现线程之间的通信。
AQS,非阻塞数据结构和原子变量类(java.util.concurrent.atomic包中的类),这些concurrent包中的基础类都是使用这种模式来实现的,而concurrent包中的高层类又是依赖于这些基础类来实现的。从整体来看,concurrent包的实现示意图如下:
- [原创] WCF技术剖析之二十二: 深入剖析WCF底层异常处理框架实现原理[中篇]
- WCF技术剖析之二十二: 深入剖析WCF底层异常处理框架实现原理[下篇]
- jsp技术:day2--jsp的几个容器、myAjax(隐藏帧技术)底层原理、jsp的三代开发技术
- CAS技术原理及分析
- WCF技术剖析之二十二: 深入剖析WCF底层异常处理框架实现原理[中篇]
- 跨平台PhoneGap技术架构四:PhoneGap底层原理(下)
- Mybatis3.3.x技术内幕(二):动态代理之投鞭断流(自动映射器Mapper的底层实现原理)
- Docker 介绍以及其相关术语、底层原理和技术
- WCF技术剖析之二十二: 深入剖析WCF底层异常处理框架实现原理[上篇]
- 单点登录的原理与CAS技术的研究
- WCF技术剖析之二十二: 深入剖析WCF底层异常处理框架实现原理[中篇]
- WCF技术剖析之二十二: 深入剖析WCF底层异常处理框架实现原理[下篇]
- tcp/ip,http,socket mysql底层技术原理
- 【Linux技术】linux黑客之网络嗅探底层原理
- Docker介绍以及其相关术语、底层原理和技术
- csdn精华目录 (技术实现,底层原理,值得关注的开发者)
- 让你5分钟内学懂,iOS开发底层原理技术~RAC深度解析
- 2018最新淘宝面试出炉:分布式锁+集群+一致Hash算法+底层技术原理
- [原创] WCF技术剖析之二十二: 深入剖析WCF底层异常处理框架实现原理[上篇]
- Spring实现Ioc之底层原理:读XML和反射技术