cas线程安全:多线程基础CAS原理深入分析
Compare And Swap,翻译成中文就是比较并交换,今天小编就来说说关于cas线程安全:多线程基础CAS原理深入分析?下面更多详细答案一起来看看吧!
cas线程安全:多线程基础CAS原理深入分析
什么是CASCompare And Swap,翻译成中文就是比较并交换。
- 操作系统层面的CAS操作,是一条CPU的原子指令:cmpxchg指令,该指令具备原子性,使用CAS操作数据时不会造成数据不一致问题。
- Java应用层面的CAS操作,是由位于sun.misc包下的Unsafe类对操作系统底层CAS原子操作进行了封装,为上层Java程序提供了CAS操作的API,这些API就是JDK5开始,新增的JUC并发包。
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);
}
}
}
/**
* @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