|
| 1 | +title: Java并发编程(一)CAS |
| 2 | +tags: |
| 3 | + - CAS |
| 4 | + - 并发 |
| 5 | +categories: |
| 6 | + - Dev |
| 7 | + - Java |
| 8 | +date: 2020-04-20 08:06:00 |
| 9 | +cover: true |
| 10 | +--- |
| 11 | + |
| 12 | +<!-- more --> |
| 13 | +## CAS 是什么 |
| 14 | +CAS 的全称 Compare-And-Swap,它是一条 CPU 并发。 |
| 15 | + |
| 16 | +它的功能是判断内存某一个位置的值是否为预期,如果是则更改这个值,这个过程就是原子的。 |
| 17 | + |
| 18 | +CAS 并发原体现在 JAVA 语言中就是 `sun.misc.Unsafe` 类中的各个方法。调用 `UnSafe` 类中的 CAS 方法,JVM 会帮我们实现出 CAS 汇编指令。这是一种完全依赖硬件的功能,通过它实现了原子操作。由于 CAS 是一种系统源语,源语属于操作系统用语范畴,是由若干条指令组成,用于完成某一个功能的过程,并且原语的执行必须是连续的,在执行的过程中不允许被中断,也就是说 CAS 是一条原子指令,不会造成所谓的数据不一致的问题。 |
| 19 | + |
| 20 | +## 比较并交换 |
| 21 | +CAS的意思就是比较并交换。上面说到,这个比较过程是原子的。我们新建一个测试类。 |
| 22 | +``` |
| 23 | +public class CASDemo { |
| 24 | + public static void main(String[] args) { |
| 25 | + checkCAS(); |
| 26 | + } |
| 27 | +
|
| 28 | + public static void checkCAS(){ |
| 29 | + AtomicInteger atomicInteger = new AtomicInteger(5); |
| 30 | + System.out.println(atomicInteger.compareAndSet(5, 2019) + "\t current data is |
| 31 | + " + atomicInteger.get()); |
| 32 | + System.out.println(atomicInteger.compareAndSet(5, 2020) + "\t current data is |
| 33 | + " + atomicInteger.get()); |
| 34 | + } |
| 35 | + atomicInteger.getAndIncrement(); |
| 36 | + System.out.println("current data is " + atomicInteger.get()); |
| 37 | +} |
| 38 | +``` |
| 39 | +查看返回结果 |
| 40 | +``` |
| 41 | +true current data is 2019 |
| 42 | +false current data is 2019 |
| 43 | +current data is 2020 |
| 44 | +``` |
| 45 | +原子整型类的初始值是5,当第一次调用compareAndSet的时候期望值是5,更新值是2019,此时的期望值和atomicInteger 值相等,则替换为更新值,输出为2019;第二次调用compareAndSet的时候期望值还是5,此时atomicInteger的值已经更新为2019,期望值和原始值不想等,不做更新操作,所以此时的atomicInteger值还是2019。 |
| 46 | + |
| 47 | +compareAndSet是AtomicInteger的一个方法 |
| 48 | +``` |
| 49 | +/** |
| 50 | + * Atomically sets the value to the given updated value |
| 51 | + * if the current value {@code ==} the expected value. |
| 52 | + * |
| 53 | + * @param expect the expected value |
| 54 | + * @param update the new value |
| 55 | + * @return {@code true} if successful. False return indicates that |
| 56 | + * the actual value was not equal to the expected value. |
| 57 | + */ |
| 58 | + public final boolean compareAndSet(int expect, int update) { |
| 59 | + return unsafe.compareAndSwapInt(this, valueOffset, expect, update); |
| 60 | + } |
| 61 | +``` |
| 62 | +他调用的是unsafe类的`compareAndSwapInt`方法,this表示当前值对象,valueOffset是当前对象在内存中的偏移量,expect为期望值,update为更新值。 |
| 63 | + |
| 64 | +## 原子性 |
| 65 | +需要说到 `atomicInteger.getAndIncrement();`这个方法,类似于i++。 |
| 66 | +``` |
| 67 | + /** |
| 68 | + * Atomically increments by one the current value. |
| 69 | + * |
| 70 | + * @return the previous value |
| 71 | + */ |
| 72 | + public final int getAndIncrement() { |
| 73 | + return unsafe.getAndAddInt(this, valueOffset, 1); |
| 74 | + } |
| 75 | +``` |
| 76 | +也是调用的unsafe类的方法。 |
| 77 | +来看一下`getAndAddInt`: |
| 78 | +``` |
| 79 | + public final int getAndAddInt(Object var1, long var2, int var4) { |
| 80 | + int var5; |
| 81 | + do { |
| 82 | + var5 = this.getIntVolatile(var1, var2); |
| 83 | + } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); |
| 84 | +
|
| 85 | + return var5; |
| 86 | + } |
| 87 | +``` |
| 88 | +var1为当前对象,var2为当前对象在内存中的偏移量,var4为1,var5为`getIntVolatile(var1, var2)`的返回值,`getIntVolatile`方法的意思是当前对象var1且内存偏移量为var2时的值是多少。 |
| 89 | + |
| 90 | +在while循环中,同样调用了`compareAndSwapInt`方法,此时的var5为期望值,var5+var4为更新值。直到比较成功。 |
| 91 | + |
| 92 | +## Unsafe类 |
| 93 | + |
| 94 | +unsafe类是CAS的核心类,由于java无法直接访问底层系统,需要通过本地(native)方法来访问,基于unsafe类可直接操作特定内存的数据unsafe类存在于sun.mics包中,其内部方法可以像c的指针一样直接操作内存。因为 Java 中 CAS 操作执行依赖于 Unsafe 类。 |
| 95 | + |
| 96 | +变量 vauleOffset,表示该变量值在内存中的偏移量,因为 Unsafe 就是根据内存偏移量来获取数据的。 |
| 97 | + |
| 98 | +变量 value 用 volatile 修饰,保证了多线程之间的内存可见性。 |
| 99 | +``` |
| 100 | +public class AtomicInteger extends Number implements java.io.Serializable { |
| 101 | + private static final long serialVersionUID = 6214790243416807050L; |
| 102 | +
|
| 103 | + // setup to use Unsafe.compareAndSwapInt for updates |
| 104 | + private static final Unsafe unsafe = Unsafe.getUnsafe(); |
| 105 | + private static final long valueOffset; |
| 106 | +
|
| 107 | + static { |
| 108 | + try { |
| 109 | + // 获取下面 value 的地址偏移量 |
| 110 | + valueOffset = unsafe.objectFieldOffset |
| 111 | + (AtomicInteger.class.getDeclaredField("value")); |
| 112 | + } catch (Exception ex) { throw new Error(ex); } |
| 113 | + } |
| 114 | +
|
| 115 | + private volatile int value; |
| 116 | + // ... |
| 117 | +} |
| 118 | +``` |
| 119 | + |
| 120 | +## CAS 的缺点 |
| 121 | + |
| 122 | + |
| 123 | +* 循环时间长开销很大 |
| 124 | + * 如果 CAS 失败,会一直尝试,如果 CAS 长时间一直不成功,可能会给 CPU 带来很大的开销(比如线程数很多,每次比较都是失败,就会一直循环),所以希望是线程数比较小的场景。 |
| 125 | +* 只能保证一个共享变量的原子操作 |
| 126 | + * 对于多个共享变量操作时,循环 CAS 就无法保证操作的原子性。 |
| 127 | +* 引出 ABA 问题 |
| 128 | + |
| 129 | +## ABA 问题 |
| 130 | + |
| 131 | +### 原子引用 |
| 132 | +``` |
| 133 | +public class AtomicRefrenceDemo { |
| 134 | + public static void main(String[] args) { |
| 135 | + User z3 = new User("张三", 22); |
| 136 | + User l4 = new User("李四", 23); |
| 137 | + AtomicReference<User> atomicReference = new AtomicReference<>(); |
| 138 | + atomicReference.set(z3); |
| 139 | + System.out.println(atomicReference.compareAndSet(z3, l4) + "\t" + atomicReference.get().toString()); |
| 140 | + System.out.println(atomicReference.compareAndSet(z3, l4) + "\t" + atomicReference.get().toString()); |
| 141 | + } |
| 142 | +} |
| 143 | +
|
| 144 | +@Getter |
| 145 | +@ToString |
| 146 | +@AllArgsConstructor |
| 147 | +class User { |
| 148 | + String userName; |
| 149 | + int age; |
| 150 | +} |
| 151 | +``` |
| 152 | + |
| 153 | +### ABA 问题是怎么产生的 |
| 154 | +``` |
| 155 | +public class ABADemo { |
| 156 | + private static AtomicReference<Integer> atomicReference = new AtomicReference<>(100); |
| 157 | +
|
| 158 | + public static void main(String[] args) { |
| 159 | + new Thread(() -> { |
| 160 | + atomicReference.compareAndSet(100, 101); |
| 161 | + atomicReference.compareAndSet(101, 100); |
| 162 | + }).start(); |
| 163 | +
|
| 164 | + new Thread(() -> { |
| 165 | + // 保证上面线程先执行 |
| 166 | + try { |
| 167 | + Thread.sleep(1000); |
| 168 | + } catch (InterruptedException e) { |
| 169 | + e.printStackTrace(); |
| 170 | + } |
| 171 | + atomicReference.compareAndSet(100, 2019); |
| 172 | + System.out.println(atomicReference.get()); // 2019 |
| 173 | + }).start(); |
| 174 | + } |
| 175 | +} |
| 176 | +``` |
| 177 | +当有一个值从 A 改为 B 又改为 A,这就是 ABA 问题。 |
| 178 | + |
| 179 | +### ABA 问题解决 |
| 180 | +时间戳原子引用 |
| 181 | +``` |
| 182 | +public class ABADemo2 { |
| 183 | + private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1); |
| 184 | +
|
| 185 | + public static void main(String[] args) { |
| 186 | + new Thread(() -> { |
| 187 | + int stamp = atomicStampedReference.getStamp(); |
| 188 | + System.out.println(Thread.currentThread().getName() + " 的版本号为:" + stamp); |
| 189 | + try { |
| 190 | + Thread.sleep(1000); |
| 191 | + } catch (InterruptedException e) { |
| 192 | + e.printStackTrace(); |
| 193 | + } |
| 194 | + atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1 ); |
| 195 | + atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1 ); |
| 196 | + }).start(); |
| 197 | +
|
| 198 | + new Thread(() -> { |
| 199 | + int stamp = atomicStampedReference.getStamp(); |
| 200 | + System.out.println(Thread.currentThread().getName() + " 的版本号为:" + stamp); |
| 201 | + try { |
| 202 | + Thread.sleep(3000); |
| 203 | + } catch (InterruptedException e) { |
| 204 | + e.printStackTrace(); |
| 205 | + } |
| 206 | + boolean b = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1); |
| 207 | + System.out.println(b); // false |
| 208 | + System.out.println(atomicStampedReference.getReference()); // 100 |
| 209 | + }).start(); |
| 210 | + } |
| 211 | +} |
| 212 | +``` |
| 213 | +输出结果 |
| 214 | +``` |
| 215 | +Thread-0 的版本号为:1 |
| 216 | +Thread-1 的版本号为:1 |
| 217 | +false |
| 218 | +100 |
| 219 | +``` |
| 220 | + |
0 commit comments