沉淀再出发:java中的CAS和ABA问题整理
Posted 精心出精品
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了沉淀再出发:java中的CAS和ABA问题整理相关的知识,希望对你有一定的参考价值。
沉淀再出发:java中的CAS和ABA问题整理
一、前言
在多并发程序设计之中,我们不得不面对并发、互斥、竞争、死锁、资源抢占等等问题,归根到底就是读写的问题,有了读写才有了增删改查,才有了所有的一切,同样的也有了谁读谁写,这样的顺序和主次问题,于是就有了上锁,乐观锁和悲观锁,同步和异步,睡眠和换入换出等问题,归根到底就是模拟了社会上的分工协作与资源共享和抢占,要理解好这些现象的本质,我们需要更加深刻地进行类比和辨析,要知道这些内容的本质就是内存和CPU之间的故事,有的时候还会有一些外存或者其他缓存。只有我们深刻的理解了这些内容背后的原理和本质,我们才能算是真正的有所感悟,于是我们就需要理解操作系统的保护模式和进程线程的运行机理,需要理解计算机组成的基本原理,明白硬件的基本结构,运行的基本单位,存储和寄存器等多种电器元件,在理解了软件和硬件的基础之上,我们还要有一些编译原理方面的知识,因为编译器将我们能看到的程序语言翻译成了一个个中间代码直到机器码,只有明白了这一点才会知道i++,这样的简单的代码其实是有两三条指令才能完成的,并不是原子操作,明白了这些我们才能够真正的理解多并发机制。
二、java的CAS原理
2.1、CAS本质
java的CAS是Compare And Swap的缩写,先进行比较再进行交换,是实现java乐观锁的一种机制。java.util.concurrent包完全建立在CAS之上的,借助CAS实现了区别于synchronouse同步锁的一种乐观锁。但是这种机制是有一定的问题的,会造成ABA问题,因此需要加时间戳(版本号)这样的机制,为什么会有悲观锁和乐观锁呢,本质上如果使用一个进程把一个资源完全锁住就称为悲观锁,直到这个进程把资源使用完之后才能解锁,这样的上锁会导致其他进程在这段时间之内一直等不到这个内存资源,但是在实际的运行之中很多进程使用内存资源都是用来读操作的,并不会修改这个内存的内容,基于这样的实际情况,如果全部使用悲观锁,在高并发的环境下必定是非常浪费时间和CPU,内存资源的,因此类似CAS这样的乐观锁就出现了,主要是满足大部分进程用来读,少部分进程用来写这样的需求的。
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存地址里面的值和A的值是一样的,那么就将内存里面的值更新成B。CAS是通过无限循环来获取数据的,若果在第一轮循环中,a线程获取地址里面的值被b线程修改了,那么a线程需要自旋,到下次循环才有可能机会执行。
也就是什么意思呢??让我们想一下,一个内存地址上可以存一个变量,如果不对这个变量进行悲观锁(同步)的加锁方式,我们在面对着很多进程的读操作的时候肯定是没问题的,毕竟内存号称“一次写入,无数次读取”的,但是如果是写操作的时候,我们就要采取一定的办法保证如果之前已经有进程修改过这块空间的值了,这次我们的修改就不能继续下去,不然的话就会造成混乱了,因此我们需要自旋等待下次的操作时机,正是因为这样的操作机制,我们对内存的使用效率也有了很大的提高。那么我们怎么判断呢?我们知道内存的地址V,并且采取的轮询的机制,在这个机制之下,我们首先读取一次V的值,记为A,之后我们运行CAS算法,这个算法是一个原子操作,为什么不直接把我们需要写入的数值B写入内存呢?!原因很简单,那就是在我们读取了V的值A之后,假如此时发生了进程切换,这个内存空间被另一个进程修改了,如果此时我们直接写入B,就把上一个进程的值给取代了,上一个进程就丢失了该信息。因此,我们在下一步需要判断一下是不是有这种操作发生,如果有的话什么都不做,继续下一轮的读V的值A1,继续比较;如果值没有变,就姑且认为一切状态没发生变化,使用原子操作,比较并且替代这个内存的值。在这种机理之下,就要求这个内存V是唯一的,也就是volatile(易变)的,只在内存中有一份,在其他地方不能被缓存。这就是CAS的本质思想。但是大家觉得有什么不妥的吗?
2.2、CAS的问题
@1、CAS容易造成ABA问题。ABA:一个线程将某一内存地址中的数值A改成了B,接着又改成了A,此时CAS认为是没有变化,其实是已经变化过了,而这个问题的解决方案可以使用版本号标识,每操作一次version加1。在java5中,已经提供了AtomicStampedReference来解决问题。
CAS操作容易导致ABA问题,也就是在做i++之间,i可能被多个线程修改过了,只不过回到了最初的值,这时CAS会认为i的值没有变。i在外面逛了一圈回来,你能保证它没有做任何坏事,不能!!也许它把b的值减了一下,把c的值加了一下等等,更有甚者如果i是一个对象,这个对象有可能是新创建出来的,i是一个引用情况又如何,所以这里面还是存在着很多问题的,解决ABA问题的方法有很多,可以考虑增加一个修改计数,只有修改计数不变的且i值不变的情况下才做i++,也可以考虑引入版本号,当版本号相同时才做i++操作等,这和事务原子性处理有点类似。
@2、CAS造成CPU利用率增加。之前说过了CAS里面是一个循环判断的过程,如果线程一直没有获取到状态,cpu资源会一直被占用。
@3、会增加程序测试的复杂度,稍不注意就会出现问题。
@4、只能保证一个共享变量的原子操作。当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,可以把多个变量放在一个对象里来进行CAS操作。
2.3、ABA的解决办法
如果一开始位置V得到的旧值是A,当进行赋值操作时再次读取发现仍然是A,并不能说明变量没有被其它线程改变过。有可能是其它线程将变量改为了B,后来又改回了A。大部分情况下ABA问题不会影响程序并发的正确性,如果要解决ABA问题,用传统的互斥同步可能比原子类更高效。可以用CAS在无锁的情况下实现原子操作,但要明确应用场合,非常简单的操作且又不想引入锁可以考虑使用CAS操作,当想要非阻塞地完成某一操作也可以考虑CAS。不推荐在复杂操作中引入CAS,会使程序可读性变差,且难以测试,同时会出现ABA问题。
ABA问题的解决办法:
1.在变量前面追加版本号:每次变量更新就把版本号加1,则A-B-A就变成1A-2B-3A。
2.atomic包下的AtomicStampedReference类:其compareAndSet方法首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用的该标志的值设置为给定的更新值。
三、具体例子
3.1、JAVA中CAS的实现
JAVA中的CAS主要使用的是Unsafe方法,Unsafe的CAS操作主要是基于硬件平台的汇编指令,目前的处理器基本都支持CAS,只不过不同的厂家的实现不一样罢了。
Unsafe提供了三个方法用于CAS操作,分别是:
1 public final native boolean compareAndSwapObject(Object value, long valueOffset, Object expect, Object update); 2 public final native boolean compareAndSwapInt(Object value, long valueOffset, int expect, int update); 3 public final native boolean compareAndSwapLong(Object value, long valueOffset, long expect, long update);
1 value 表示 需要操作的对象 2 valueOffset 表示 对象(value)的地址的偏移量(通过Unsafe.objectFieldOffset(Field valueField)获取) 3 expect 表示更新时value的期待值 4 update 表示将要更新的值
具体过程为每次在执行CAS操作时,线程会根据valueOffset去内存中获取当前值去跟expect的值做对比如果一致则修改并返回true,如果不一致说明有别的线程也在修改此对象的值,则返回false。
Unsafe类中compareAndSwapInt的具体实现:
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
UnsafeWrapper("Unsafe_CompareAndSwapInt");
oop p = JNIHandles::resolve(obj);
jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END
3.2、AtomicInteger类中实现CAS的方法
1 /* 2 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14 * 15 * 16 * 17 * 18 * 19 * 20 * 21 * 22 * 23 */ 24 25 /* 26 * 27 * 28 * 29 * 30 * 31 * Written by Doug Lea with assistance from members of JCP JSR-166 32 * Expert Group and released to the public domain, as explained at 33 * http://creativecommons.org/publicdomain/zero/1.0/ 34 */ 35 36 package java.util.concurrent.atomic; 37 import java.util.function.IntUnaryOperator; 38 import java.util.function.IntBinaryOperator; 39 import sun.misc.Unsafe; 40 41 /** 42 * An {@code int} value that may be updated atomically. See the 43 * {@link java.util.concurrent.atomic} package specification for 44 * description of the properties of atomic variables. An 45 * {@code AtomicInteger} is used in applications such as atomically 46 * incremented counters, and cannot be used as a replacement for an 47 * {@link java.lang.Integer}. However, this class does extend 48 * {@code Number} to allow uniform access by tools and utilities that 49 * deal with numerically-based classes. 50 * 51 * @since 1.5 52 * @author Doug Lea 53 */ 54 public class AtomicInteger extends Number implements java.io.Serializable { 55 private static final long serialVersionUID = 6214790243416807050L; 56 57 // setup to use Unsafe.compareAndSwapInt for updates 58 private static final Unsafe unsafe = Unsafe.getUnsafe(); 59 private static final long valueOffset; 60 61 static { 62 try { 63 valueOffset = unsafe.objectFieldOffset 64 (AtomicInteger.class.getDeclaredField("value")); 65 } catch (Exception ex) { throw new Error(ex); } 66 } 67 68 private volatile int value; 69 70 /** 71 * Creates a new AtomicInteger with the given initial value. 72 * 73 * @param initialValue the initial value 74 */ 75 public AtomicInteger(int initialValue) { 76 value = initialValue; 77 } 78 79 /** 80 * Creates a new AtomicInteger with initial value {@code 0}. 81 */ 82 public AtomicInteger() { 83 } 84 85 /** 86 * Gets the current value. 87 * 88 * @return the current value 89 */ 90 public final int get() { 91 return value; 92 } 93 94 /** 95 * Sets to the given value. 96 * 97 * @param newValue the new value 98 */ 99 public final void set(int newValue) { 100 value = newValue; 101 } 102 103 /** 104 * Eventually sets to the given value. 105 * 106 * @param newValue the new value 107 * @since 1.6 108 */ 109 public final void lazySet(int newValue) { 110 unsafe.putOrderedInt(this, valueOffset, newValue); 111 } 112 113 /** 114 * Atomically sets to the given value and returns the old value. 115 * 116 * @param newValue the new value 117 * @return the previous value 118 */ 119 public final int getAndSet(int newValue) { 120 return unsafe.getAndSetInt(this, valueOffset, newValue); 121 } 122 123 /** 124 * Atomically sets the value to the given updated value 125 * if the current value {@code ==} the expected value. 126 * 127 * @param expect the expected value 128 * @param update the new value 129 * @return {@code true} if successful. False return indicates that 130 * the actual value was not equal to the expected value. 131 */ 132 public final boolean compareAndSet(int expect, int update) { 133 return unsafe.compareAndSwapInt(this, valueOffset, expect, update); 134 } 135 136 /** 137 * Atomically sets the value to the given updated value 138 * if the current value {@code ==} the expected value. 139 * 140 * <p><a href="package-summary.html#weakCompareAndSet">May fail 141 * spuriously and does not provide ordering guarantees</a>, so is 142 * only rarely an appropriate alternative to {@code compareAndSet}. 143 * 144 * @param expect the expected value 145 * @param update the new value 146 * @return {@code true} if successful 147 */ 148 public final boolean weakCompareAndSet(int expect, int update) { 149 return unsafe.compareAndSwapInt(this, valueOffset, expect, update); 150 } 151 152 /** 153 * Atomically increments by one the current value. 154 * 155 * @return the previous value 156 */ 157 public final int getAndIncrement() { 158 return unsafe.getAndAddInt(this, valueOffset, 1); 159 } 160 161 /** 162 * Atomically decrements by one the current value. 163 * 164 * @return the previous value 165 */ 166 public final int getAndDecrement() { 167 return unsafe.getAndAddInt(this, valueOffset, -1); 168 } 169 170 /** 171 * Atomically adds the given value to the current value. 172 * 173 * @param delta the value to add 174 * @return the previous value 175 */ 176 public final int getAndAdd(int delta) { 177 return unsafe.getAndAddInt(this, valueOffset, delta); 178 } 179 180 /** 181 * Atomically increments by one the current value. 182 * 183 * @return the updated value 184 */ 185 public final int incrementAndGet() { 186 return unsafe.getAndAddInt(this, valueOffset, 1) + 1; 187 } 188 189 /** 190 * Atomically decrements by one the current value. 191 * 192 * @return the updated value 193 */ 194 public final int decrementAndGet() { 195 return unsafe.getAndAddInt(this, valueOffset, -1) - 1; 196 } 197 198 /** 199 * Atomically adds the given value to the current value. 200 * 201 * @param delta the value to add 202 * @return the updated value 203 */ 204 public final int addAndGet(int delta) { 205 return unsafe.getAndAddInt(this, valueOffset, delta) + delta; 206 } 207 208 /** 209 * Atomically updates the current value with the results of 210 * applying the given function, returning the previous value. The 211 * function should be side-effect-free, since it may be re-applied 212 * when attempted updates fail due to contention among threads. 213 * 214 * @param updateFunction a side-effect-free function 215 * @return the previous value 216 * @since 1.8 217 */ 218 public final int getAndUpdate(IntUnaryOperator updateFunction) { 219 int prev, next; 220 do { 221 prev = get(); 222 next = updateFunction.applyAsInt(prev); 223 } while (!compareAndSet(prev, next)); 224 return prev; 225 } 226 227 /** 228 * Atomically updates the current value with the results of 229 * applying the given function, returning the updated value. The 230 * function should be side-effect-free, since it may be re-applied 231 * when attempted updates fail due to contention among threads. 232 * 233 * @param updateFunction a side-effect-free function 234 * @return the updated value 235 * @since 1.8 236 */ 237 public final int updateAndGet(IntUnaryOperator updateFunction) { 238 int prev, next; 239 do { 240 prev = get(); 241 next = updateFunction.applyAsInt(prev); 242 } while (!compareAndSet(prev, next)); 243 return next; 244 } 245 246 /** 247 * Atomically updates the current value with the results of 248 * applying the given function to the current and given values, 249 * returning the previous value. The function should be 250 * side-effect-free, since it may be re-applied when attempted 251 * updates fail due to contention among threads. The function 252 * is applied with the current value as its first argument, 253 * and the given update as the second argument. 254 * 255 * @param x the update value 256 * @param accumulatorFunction a side-effect-free function of two arguments 257 * @return the previous value 258 * @since 1.8 259 */ 260 public final int getAndAccumulate(int x, 261 IntBinaryOperator accumulatorFunction) { 262 int prev, next; 263 do { 264 prev = get(); 265 next = accumulatorFunction.applyAsInt(prev, x); 266 } while (!compareAndSet(prev, next)); 267 return prev; 268 } 269 270 /** 271 * Atomically updates the current value with the results of 272 * applying the given function to the current and given values, 273 * returning the updated value. The function should be 274 * side-effect-free, since it may be re-applied when attempted 275 * updates fail due to contention among threads. The function 276 * is applied with the current value as its first argument, 277 * and the given update as the second argument. 278 * 279 * @param x the update value 280 * @param accumulatorFunction a side-effect-free function of two arguments 281 * @return the updated value 282 * @since 1.8 283 */ 284 public final int accumulateAndGet(int x, 285 IntBinaryOperator accumulatorFunction) { 286 int prev, next; 287 do { 288 prev = get(); 289 next = accumulatorFunction.applyAsInt(prev, x); 290 } while (!compareAndSet(prev, next)); 291 return next; 292 } 293 294 /** 295 * Returns the String representation of the current value. 296 * @return the String representation of the current value 297 */ 298 public String toString() { 299 return Integer.toString(get()); 300 } 301 302 /** 303 * Returns the value of this {@code AtomicInteger} as an {@code int}. 304 */ 305 public int intValue() { 306 return get(); 307 } 308 309 /** 310 * Returns the value of this {@code AtomicInteger} as a {@code long} 311 * after a widening primitive conversion. 312 * @jls 5.1.2 Widening Primitive Conversions 313 */ 314 public long longValue() { 315 return (long)get(); 316 } 317 318 /** 319 * Returns the value of this {@code AtomicInteger} as a {@code float} 320 * after a widening primitive conversion. 321 * @jls 5.1.2 Widening Primitive Conversions 322 */ 323 public float floatValue() { 324 return (float)get(); 325 } 326 327 /** 沉淀再出发:如何在eclipse中查看java的核心代码