JAVA中的CAS
一、cas概念与原理
cas,全称compare and swap(比较与交换),解决多线程并行情况下使用锁造成性能损耗的一种机制。
实现思想 cas(v, a, b),v为内存地址、a为预期原值,b为新值。如果内存地址的值与预期原值相匹配,那么将该位置值更新为新值。否则,说明已经被其他线程更新,处理器不做任何操作;无论哪种情况,它都会在 cas 指令之前返回该位置的值。而我们可以使用自旋锁,循环cas,重新读取该变量再尝试再次修改该变量,也可以放弃操作。
cas操作由处理器提供支持,是一种原语。原语是操作系统或计算机网络用语范畴。是由若干条指令组成的,用于完成一定功能的一个过程,具有不可分割性,即原语的执行必须是连续的,在执行过程中不允许被中断。如 intel 处理器,比较并交换通过指令的 cmpxchg 系列实现。处理器相关指令不做过多介绍,有兴趣的可自行查阅资料。
二、jdk1.8 中的cas
unsafe类,在sun.misc包下,不属于java标准。unsafe类提供一系列增加java语言能力的操作,如内存管理、操作类/对象/变量、多线程同步等。其中与cas相关的方法有以下几个:
//var1为cas操作的对象,offset为var1某个属性的地址偏移值,expected为期望值,var2为要设置的值,利用jni来完成cpu指令的操作 public final native boolean compareandswapobject(object var1, long offset, object expected, object var2); public final native boolean compareandswapint(object var1, long offset, int expected, int var2); public final native boolean compareandswaplong(object var1, long offset, long expected, long var2);
/** 如果cas成功,return oldvalue, oldvalue = oldvalue + addvalue * 如果cas失败,自旋,一直运行,直到成功为止 */ public final xxx getandaddxxx(object var1, long offset, long addvalue) { int oldvalue; do { oldvalue = this.getintvolatile(var1, offset); } while(!this.compareandswapint(var1, offset, oldvalue, oldvalue + addvalue)); return oldvalue; } /** 如果cas成功,return oldvalue, oldvalue = newvalue * 如果cas失败,自旋,一直运行,直到成功为止 */ public final xxx getandsetxxx(object var1, long offset, object newvalue) { int oldvalue; do { oldvalue = this.getxxxvolatile(var1, offset); } while(!this.compareandswapxxx(var1, offset, oldvalue, newvalue)); return oldvalue; }
一般不建议使用unsafe类,除非对它有很深入的了解。
java.util.concurrent包中大量使用了cas原理,如atomicinteger类,都是调用上面几个unsafe方法保证多线程数据的正确性
以下是atomicinteger的cas操作相关源码
1 public class atomicinteger extends number implements java.io.serializable { 2 private static final long serialversionuid = 6214790243416807050l; 3 4 // setup to use unsafe.compareandswapint for updates 5 // unsafe类,提供一系列增强java的功能,如内存管理、操作类/对象/变量、多线程同步等。不建议开发者调用 6 private static final unsafe unsafe = unsafe.getunsafe(); 7 // 获取对象某个属性的地址偏移值 8 private static final long valueoffset; 9 10 static { 11 try { 12 // value相对“起始地址”的偏移量 13 valueoffset = unsafe.objectfieldoffset 14 (atomicinteger.class.getdeclaredfield("value")); 15 } catch (exception ex) { throw new error(ex); } 16 } 17 18 // value值, volatile修饰,保证不同线程间的可见性 19 private volatile int value; 20 public atomicinteger(int initialvalue) { value = initialvalue; } 21 public atomicinteger() {} 22 23 public final int get() { return value; } 24 public final void set(int newvalue) { value = newvalue; } 25 26 /** 27 * eventually sets to the given value. 28 * 29 * @param newvalue the new value 30 * @since 1.6 31 */ 32 public final void lazyset(int newvalue) { 33 //有序或者有延迟的putintvolatile方法 34 unsafe.putorderedint(this, valueoffset, newvalue); 35 } 36 37 /** 38 * atomically sets to the given value and returns the old value. 39 * @param newvalue the new value 40 * @return the previous value 41 */ 42 public final int getandset(int newvalue) { 43 return unsafe.getandsetint(this, valueoffset, newvalue); 44 } 45 46 /** 47 * atomically sets the value to the given updated value 48 * if the current value {@code ==} the expected value. 49 * @param expect the expected value 50 * @param update the new value 51 * @return {@code true} if successful. false return indicates that 52 * the actual value was not equal to the expected value. 53 */ 54 public final boolean compareandset(int expect, int update) { 55 // jni调用,实现cas 56 return unsafe.compareandswapint(this, valueoffset, expect, update); 57 } 58 59 /** 60 * i++ 操作 61 * atomically increments by one the current value. 62 * @return the previous value 63 */ 64 public final int getandincrement() { 65 return unsafe.getandaddint(this, valueoffset, 1); 66 } 67 68 /** 69 * i-- 操作 70 * atomically decrements by one the current value. 71 * @return the previous value 72 */ 73 public final int getanddecrement() { 74 return unsafe.getandaddint(this, valueoffset, -1); 75 } 76 77 /** 78 * return i, i = i + n 操作 79 * atomically adds the given value to the current value. 80 * @param delta the value to add 81 * @return the previous value 82 */ 83 public final int getandadd(int delta) { 84 return unsafe.getandaddint(this, valueoffset, delta); 85 } 86 87 /** 88 * ++i 操作 89 * atomically increments by one the current value. 90 * @return the updated value 91 */ 92 public final int incrementandget() { 93 return unsafe.getandaddint(this, valueoffset, 1) + 1; 94 } 95 96 /** 97 * --i 操作 98 * atomically decrements by one the current value. 99 * @return the updated value 100 */ 101 public final int decrementandget() { 102 return unsafe.getandaddint(this, valueoffset, -1) - 1; 103 } 104 105 /** 106 * i = i + n ,return i操作 107 * atomically adds the given value to the current value. 108 * @param delta the value to add 109 * @return the updated value 110 */ 111 public final int addandget(int delta) { 112 return unsafe.getandaddint(this, valueoffset, delta) + delta; 113 } 114 // 其余函数,略... 115
三、cas缺点
cas有几个缺点:
1、aba问题。当第一个线程执行cas操作,尚未修改为新值之前,内存中的值已经被其他线程连续修改了两次,使得变量值经历 a -> b -> a的过程。
解决方案:添加版本号作为标识,每次修改变量值时,对应增加版本号; 做cas操作前需要校验版本号。jdk1.5之后,新增atomicstampedreference类来处理这种情况。
2、循环时间长开销大。如果有很多个线程并发,cas自旋可能会长时间不成功,会增大cpu的执行开销。
3、只能对一个变量进原子操作。jdk1.5之后,新增atomicreference类来处理这种情况,可以将多个变量放到一个对象中。