cas是什么在java中的应用 Java并发系列CAS与原子变量
CAS
CAS(Compare-And-Swap)是CPU的原子指令,中文翻译成比较交换,汇编指令为CMPXCHG。
CAS操作包含三个操作数内存值V、预期原值A和新值B,当且仅当预期原值A和内存值V相同时,将内存值V修改为新值B,否则,处理器不做任何操作。
// CAS C 示意 int compare_and_swap (int* reg, int oldval, int newval) { int old_reg_val = *reg; ATOMIC(); if(old_reg_val == oldval) *reg = newval; END_ATOMIC(); return old_reg_val; }
CAS:"我认为位置V应该包含值A;如果包含该值,则将B放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可“
CAS是一种乐观加锁策略,在进行争用操作(读-修改-写)时操作线程总是认为操作会成功,如果不成功则立即返回,确保一个时刻只有一个线程写成功。相比基于MutexLock线程互斥,CAS原子操作不用挂起线程,减少了调度线程的资源开销,另外大部分CUP都支持CAS指令,硬件级的同步原语也使得CAS更加轻量级。
CAS用在了哪里?
1、JVM对内置锁synchronized的优化中引入的偏向锁和轻量都是基于CAS实现的,使用CAS操作替换对象头中的ThreadID和MarkWord来实加锁和解锁,比重量级锁效率有所提升。
2、CAS更是整个J.U.C包的基石,原子变量、同步器、并发容器等都大量的依赖CAS。
J.U.C是java.util.concurrent包的简称,鼎鼎大名的Doug Lea的作品,在J.U.C之前,java并发编程只能靠final、volatile、sychronized三个同步原语实现,J.U.C中实现了很多高效的同步工具、原子变量、并发容器等等。
JUC实现依赖
ABA问题
CAS操作时以预期原值和当前内存值是否相等作为是否修改的依据,这就会出现ABA问题。所谓ABA是指如果变量V的值原来A,变成了B,又变成了A,CAS进行检查时会发现它的值没有发生变化,但实际上变量V已经被修改过了。如果只关注最终结果,不关注中间状态是如何变化的,那么绝大部分情况下ABA问题是不会对操作结果什么影响的。
但是如果需要关注变量的变化过程,比如,一个链表A-B-C,线程1执行CAS(A,B)准备将头结点换成B,这时线程2先执行了CAS(B,C),此时链表为A-C,节点B被拿出了,CUP切换到线程1,线程1发现节点A没有变化将节点A换成节点B,因为B-next为null,节点C也被从链表上删除了,因为链表的头节点没有发生变化,所以CAS操作链表头是允许的,但是链表本身已经发生过变化。
ABA问题对程序的影响:会漏掉一些监控时间窗口,对于依赖过程值的运算会产生影响。
解决ABA问题的方法就是给变量增加版本号,每一次CAS更新操作都对版本号进行累加,变量值和版本号同时作为CAS比较的目标,只要能保证版本号一直累加就不会出现ABA问题,J.U.C中的有专门处理这个问题的类。
Unsafe
sun.misc.Unsafe是一个特殊的类,顾名思义"不安全",它包含了一些系统底层的指令:
/**返回指定field内存地址偏移量*/ public native long objectFieldOffset(Field field); /**返回指定静态field内存地址偏移量*/ public native long staticFieldOffset(Field field); /**获取给定数组中第一个元素的偏移地址*/ public native int arrayBaseOffset(Class arrayClass); /**CAS设置Int*/ public native boolean compareAndSwapInt(Object obj, long offset, int expect, int update); /**CAS设置Long*/ public native boolean compareAndSwapLong(Object obj, long offset, long expect, long update); /**CAS设置Ojbect*/ public native boolean compareAndSwapObject(Object obj, long offset, Object expect, Object update); /*** * 设置obj对象中offset偏移地址对应的整型field的值为指定值。 * 这是一个有序或者有延迟的方法,并且不保证值的改变被其他线程立即看到。 * 只有在field被修饰并且期望被意外修改的时候使用才有用。 */ public native void putOrderedInt(Object obj, long offset, int value); public native void putOrderedLong(Object obj, long offset, long value); public native void putOrderedObject(Object obj, long offset, Object value); /*** * 设置obj对象中offset偏移地址对应的整型field的值为指定值。 * 支持volatile store语义 */ public native void putIntVolatile(Object obj, long offset, int value); public native void putLongVolatile(Object obj, long offset, long value); public native void putObjectVolatile(Object obj, long offset, Object value); /*** * 设置obj对象中offset偏移地址对应的long型field的值为指定值。 */ public native void putInt(Object obj, long offset, int value); public native void putLong(Object obj, long offset, long value); public native void putObject(Object obj, long offset, Object value); /*** * 获取obj对象中offset偏移地址对应的整型field的值,支持volatile load语义。 */ public native int getIntVolatile(Object obj, long offset); public native long getLongVolatile(Object obj, long offset); public native Object getObjectVolatile(Object obj, long offset); /*** * 获取obj对象中offset偏移地址对应类型field的值 */ public native long getInt(Object obj, long offset); public native long getLong(Object obj, long offset); public native Object getObject(Object obj, long offset); /**挂起线程*/ public native void park(boolean isAbsolute, long time); /**唤醒线程*/ public native void unpark(Thread thread); /**内存操作*/ public native long allocateMemory(long l); public native long reallocateMemory(long l, long l1); public native void freeMemory(long l);
compareAndSwapXXX就是CAS方法,分别对应int,long,Object三种数据类型的CAS操作,参数o是需要更新的对象、offset是对象字段在内存中的偏移量,expected是对象字段的预期原值,x是要更新的新值。
park、unpark分别是挂起和唤醒线程。
putXXXVolatile、getXXXVolatile方法是以Volatile语义设置和获取属性值。
XXXMemory方法是内存分配的操作,在java NIO和netty中会大量使用。
向来以安全著称的java肯定不会把这个类开放给用户使用,Unsafe在构造方法和工厂方法中做了限制。
private Unsafe() {} private static final Unsafe theUnsafe = new Unsafe(); public static Unsafe getUnsafe() { Class cc = sun.reflect.Reflection.getCallerClass(2); if (cc.getClassLoader() != null) throw new SecurityException("Unsafe"); return theUnsafe; }
jre/目录下的核心库是使用BootstrapClassLoader来加载的,它是虚拟机的一部分用C 编写,JAVA代码根本无获取它的引用。所以"cc"为空说明是jdk核心类库调用返回theUnsafe,不为空说明是非jdk核心类库调用就会抛出异常。
但是可以通过反射的方式来获取Unsafe实例,测试下还是可以的,但是在项目中千万别乱用。
// 反射获取Unsafe public static Unsafe getUnsafe() { Field theUnsafe = null; Unsafe instance = null; try { theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); theUnsafe.setAccessible(true); instance = (Unsafe) theUnsafe.get(null); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } return instance; }
原子变量
java.util.concurrent.atomic包中,提供了包含基本类型和引用类型的12中原子变量:
基本类型
- AtomicBoolean 原子布尔型
- AtomicInteger 原子整型
- AtomicLong 原子长整型
引用类型
- AtomicReference 原子引用类型
- AtomicMarkableReference原子标记位引用类型
- AtomicStampedReference原子版本号引用类型
字段类型
- AtomicIntegerFieldUpdater原子整型字段更新器
- AtomicLongFieldUpdater原子长整型字段更新器
- AtomicReferenceFieldUpdater原子应用字段更新器
数组类型
- AtomicIntegerArray 原子整型数组
- AtomicLongArray 原子长整型数组
- AtomicReferenceArray 原子引用数组
原子变量,能够在不使用同步操作的情况下具有原子性。而Volatile关键字只能保证变量单次操作的原子性,"i "这样的复合操作要想使其具有原子性只能加锁同步。
AtomicInteger实现分析
//int 值 private volatile int value; public final int get() { return value; } // 省略代码 ... ... // 累加,并返回旧值 public final int getAndIncrement() { for (;;) {//循环CAS int current = get();// 获取当前值 int next = current 1;// //累加后的值 新值 if (compareAndSet(current, next)) return current;//CAS 更新,如果更新成功返回 } } //unsafe CAS 更新 public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
value变量是volatile修饰的,具有可见性,get()方法获取的当前值肯定是最新值。
将其current 1作为新值next,在循环体中进行CAS设置,直到成功才退出在此期间不用担心其他线程的干扰,因为一旦有其他线程视图修改value,就会致使compareAndSet失败并立即返回重试。
getAndIncrement和incrementAndGet是两个常用的方法,get在前说是先返回再加1即返回来的是旧值,get在后返回的是新值。
AtomicIntegerFieldUpdater使用方法
AtomicIntegerFieldUpdater用来CAS操作对象里面的int类型的字段,是一个抽象类,其具体实现由私有内部类AtomicIntegerFieldUpdaterImpl完成,AtomicIntegerFieldUpdater无法直接实例化,需使用静态工厂方法newUpdater获取其实例。
static class Person { volatile int age; public Person(int age) { this.age = age; } } public void testUpdate() { Person person = new Person(30); AtomicIntegerFieldUpdater<Person> updater = AtomicIntegerFieldUpdater.newUpdater(Person.class, "age"); boolean succeed = updater.compareAndSet(person, 30, 31); Assert.true(succeed); boolean failure = updater.compareAndSet(person, 32, 33); Assert.false(failure); }
要更新的字段age必须是volatile修饰的,因为要保证其可见性,同时也要保证字段是可访问的,如果是更新方法compareAndSet()和字段在同一个类中,字段可以是protected和private的,否则必须public的。
另外两种实现:
AtomicLongFieldUpdater长整型字段原子更新器
AtomicReferenceFieldUpdater引用字段原子更新器
AtomicStampedReference实现分析
AtomicStampedReference是避免ABA问题的引用类型,内部布局:
public class AtomicStampedReference <V> { //内部类 private static class Pair<T> { final T reference;//引用 final int stamp;//版本号 //构造方法 private Pair(T reference, int stamp) { this.reference = reference; this.stamp = stamp; } //工厂方法 static <T> Pair<T> of(T reference, int stamp) { return new Pair<T>(reference, stamp); } } private volatile Pair<V> pair;//Pair实例 volatile可见性 //构造方法 public AtomicStampedReference(V initialRef, int initialStamp) { pair = Pair.of(initialRef, initialStamp); } // 省略代码... .... /** * @param expectedReference 期望原值 * @param newReference 新值 * @param expectedStamp 原版本号 * @param newStamp 新版本号 */ public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) { Pair<V> current = pair; return expectedReference == current.reference && expectedStamp == current.stamp && ((newReference == current.reference && newStamp == current.stamp) || casPair(current, Pair.of(newReference, newStamp))); } }
内部类Pair封装了引用对象reference和版本号stamp。
如果期望值expectedReference等于当前值current.reference并且期望版本号expectedStamp等于当前版本号current.stamp才会往下进行,否则,直接返回false,设置失败。
如果当新值newReference等于当前值current.reference新版本号newStamp等于当前版本号current.stamp,也没必要CAS操。
其他类型原子变量的实现方式都是使用Unsafe对象进行compareAndSwap操作,不一一累述。
小结
1:CAS作为硬件同步原语,相比基于线程互斥的同步操作要更加轻量级
2:volatile的可见性加上CAS原子操作是J.U.C无锁并发实现的基础
3:原子变量可以使变量在无锁的情况下保持原子性。
,
免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com