cas线程安全:多线程基础CAS原理深入分析

Compare And Swap,翻译成中文就是比较并交换,今天小编就来说说关于cas线程安全:多线程基础CAS原理深入分析?下面更多详细答案一起来看看吧!

cas线程安全:多线程基础CAS原理深入分析

cas线程安全:多线程基础CAS原理深入分析

什么是CAS

Compare And Swap,翻译成中文就是比较并交换

  • 操作系统层面的CAS操作,是一条CPU的原子指令:cmpxchg指令,该指令具备原子性,使用CAS操作数据时不会造成数据不一致问题。
  • Java应用层面的CAS操作,是由位于sun.misc包下的Unsafe类对操作系统底层CAS原子操作进行了封装,为上层Java程序提供了CAS操作的API,这些API就是JDK5开始,新增的JUC并发包。
CAS在Java中的应用

Java中的CAS操作,依赖于sun.misc.Unsafe类实现,该类提供native方法,直接调用操作系统的CAS指令。

关于Unsafe类的使用,包括以下方面:

获取Unsafe实例

public class JvmUtil { /** * 获取Unsafe实例对象 * sun.misc.Unsafe类被定义成final类型,因此不允许被继承,其构造方法为private的方法 * 因此无法在外部对Unsafe进行实例化,可以通过反射的方式,自定义获取Unsafe实例 * 非必要不要直接用该类!!! * @return Unsafe实例 */ public static Unsafe getUnsafe() { try { //theUnsafe为sun.misc.Unsafe类中定义的私有变量 //private static final Unsafe theUnsafe Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); theUnsafe.setAccessible(true); return (Unsafe) theUnsafe.get(null); } catch (Exception e) { throw new AssertionError(e); } } }

调用Unsafe提供的CAS方法

/** * @param o 需要操作的字段所处的对象 * @param offset 需要操作的字段相对于对象头的偏移量 * @param expected 期望值(旧值) * @param update 更新值(新值) * @return true 更新成功 | false 更新失败 */ public final native boolean compareAndSwapObject( Object o, long offset, Object expected, Object update); public final native boolean compareAndSwapInt( Object o, long offset, int expected, int update); public final native boolean compareAndSwapLong( Object o, long offset, long expected, long update);

以上方法直接通过native方式(封装C 代码)调用了底层的CPU指令cmpxchg。

在执行Unsafe的CAS方法时,这些方法首先将内存位置的值期望值(旧值)比较,如果相匹配,那么处理器会自动将该内存位置的值更新为新值,并返回true;如果不匹配,处理器不做任何操作,并返回false。

调用Unsafe提供的字段偏移量方法

//获取静态属性Field在Class对象中的偏移量 public native long staticFieldOffset(Field field); //获取非静态属性Field在Class对象中的偏移量,相对于Object对象头部的偏移量,相对的内存地址 public native long objectFieldOffset(Field field);

字段偏移量计算时,首先确定需要计算的字段是什么属性的,是静态的还是非静态的。若是静态的成员,则属于类的成员而不是对象的成员。

CAS的优势主要有两点:

  • CAS属于无锁编程,线程之间不存在阻塞和唤醒这些重量级的操作;
  • 进程不存在用户态和内核态之间的运行切换,进程不需要承担频繁切换带来的开销。

尽管使用CAS进行无锁编程具备以上优势,但是仍然存在弊端。

CAS的弊端及规避措施1、ABA问题

使用CAS操作内存数据时,当数据发生过变化也能更新成功,如在操作序列A-->B-->A时,最后一个元素CAS的预期值A实际已经发生过更改,但也能更新成功,这就产生了ABA问题。

ABA问题的解决思路就是使用版本号。在变量前面或后面追加上版本号,每次更新变量时将版本号加1,那么操作序列A-->B-->A就变成了A1-->B2-->A3,如果将A1当做A3的期望值,就会操作失败。

JDK提供了AtomicStampedReference和AtomicMarkableReference两个类来解决ABA问题。比较常用的是AtomicStampedReference类,该类的compareAndSet方法会首先检查当前引用是否等于预期引用,再检查当前印戳是否等于预期印戳,如果全部相等,则以原子方式将引用和印戳设置为新值。

2、只能保证一个共享变量的原子操作

当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,CAS就无法保证操作的原子性。

一个比较简单的规避方法,把多个共享变量合并成一个共享变量来操作。

JDK提供了AtomicReference类来保证引用对象之间的原子性,可以把多个变量放在AtomicReference实例后,再进行CAS操作。例如有两个共享变量i=1、j=2,可以将二者合并成一个对象,然后用CAS来操作合并对象的AtomicReference引用。

3、无效CAS会带来额外开销

自旋CAS如果长时间不成功,会给CPU带来极大的开销,从而降低程序的性能。可以使用限制自旋次数的方案解决。

4、部分CPU平台上存在“总线风暴”问题

CAS操作和volatile一样,也需要CPU通过MESI协议保证各个内核的“Cache一致性”。CPU通过BUS总线发送大量的MESI协议相关的信息,产生“Cache一致性流量”。因为总线的“通信能力”被设计成固定的,如果Cache一致性流量过大,总线将成为瓶颈,这就是所谓的“总线风暴”。

CAS在JDK中的广泛应用
  • JUC的atomic包中的原子类:使用CAS保障对数字成员操作的原子性。
  • Java AQS:通过CAS保障其内部双向队列头部、尾部操作的原子性
  • 显示锁、CurrentHashMap:基于AQS、JUC.atomic包中的原子类实现。
,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页