cas是什么在java中的应用 Java并发系列CAS与原子变量

cas是什么在java中的应用 Java并发系列CAS与原子变量(1)

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中实现了很多高效的同步工具、原子变量、并发容器等等。

cas是什么在java中的应用 Java并发系列CAS与原子变量(2)

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中原子变量:

基本类型

  1. AtomicBoolean 原子布尔型
  2. AtomicInteger 原子整型
  3. AtomicLong 原子长整型

引用类型

  1. AtomicReference 原子引用类型
  2. AtomicMarkableReference原子标记位引用类型
  3. AtomicStampedReference原子版本号引用类型

字段类型

  1. AtomicIntegerFieldUpdater原子整型字段更新器
  2. AtomicLongFieldUpdater原子长整型字段更新器
  3. AtomicReferenceFieldUpdater原子应用字段更新器

数组类型

  1. AtomicIntegerArray 原子整型数组
  2. AtomicLongArray 原子长整型数组
  3. 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:原子变量可以使变量在无锁的情况下保持原子性。

cas是什么在java中的应用 Java并发系列CAS与原子变量(3)

,

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

    分享
    投诉
    首页