面试三、多线程之cas/automic
1、CAS
1.1、以compareAndSwapInt为例:
compareAndSwapInt(ojb, offset, expect, update),表示在obj对象offset位置的值是否等于expect,如等于则更新为update。
1.2、存在问题
1.2.1、ABA:
第一步、线程1获取内存数据为A
第二步、线程2修改数据为B
第三步、线程2修改数据为A
第四步、线程1进行CAS操作
在第四步中因为内存数据为A,cas操作成功,但其实数据已经被线程2更改过
解决办法:加版本号,每次校验内存值和版本号,只有两者都一致才cas成功。例:AutomicStampedReference
1.2.2、高竞争下,cas频繁失败一直重试导致开销大
1.2.3、使用限制条件高,只能保证单个变量操作的原子性
2、Automic
2.1、AutomicInteger/AutomicLong:在并发情况下如果要实现计数器,可以用这两个类来实现。
以AutomicInteger.getAndAdd()为例:
AutomicInteger在类加载初始化时会获取value的偏移值设置在valueOffset,value是线程可见的。
当调用getAndAdd()时,
27行:先取出value当前的值
28行:调用cas将value+delta设置进去。如果cas失败说明value值有其他线程改了,自旋一直到成功
1 // setup to use Unsafe.compareAndSwapInt for updates 2 private static final Unsafe unsafe = Unsafe.getUnsafe(); 3 private static final long valueOffset; 4 5 static { 6 try { 7 valueOffset = unsafe.objectFieldOffset 8 (AtomicInteger.class.getDeclaredField("value")); 9 } catch (Exception ex) { throw new Error(ex); } 10 } 11 12 private volatile int value; 13 14 /** 15 * Atomically adds the given value to the current value. 16 * 17 * @param delta the value to add 18 * @return the previous value 19 */ 20 public final int getAndAdd(int delta) { 21 return unsafe.getAndAddInt(this, valueOffset, delta); 22 } 23 24 public final int getAndAddInt(Object var1, long var2, int var4) { 25 int var5; 26 do { 27 var5 = this.getIntVolatile(var1, var2); 28 } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); 29 30 return var5; 31 }
2.2、LongAddr:jdk1.8加入,在并发大的情况下比AutomicLong优秀,代价是占用内存更多。
引入了连个参数base和cells,base是在竞争不激烈情况下累加值直接更新在base上,当竞争激烈时会根据每个线程的hash来分配cell并将累加值更新在cell里。
sum+cell[]累计就是总和。设计理念是通过把不同线程分配到不同cell来减少冲突的概率,类似jdk1.7的concurrentHashMap的segment。
以LongAddr.add()为例
8行:先尝试cas操作base,如果竞争不激烈则cas直接成功返回
12行:计算线程hash,如果有对应cell则尝试cas操作cell
13行:如果cell是空、线程hash没有找到对应cell、cas操作cell失败的情况调用longAccumulate方法
1 /** 2 * Adds the given value. 3 * 4 * @param x the value to add 5 */ 6 public void add(long x) { 7 Cell[] as; long b, v; int m; Cell a; 8 if ((as = cells) != null || !casBase(b = base, b + x)) { 9 boolean uncontended = true; 10 if (as == null || (m = as.length - 1) < 0 || 11 (a = as[getProbe() & m]) == null || 12 !(uncontended = a.cas(v = a.value, v + x))) 13 longAccumulate(x, null, uncontended); 14 } 15 } 16 17 /** 18 * Returns the current sum. The returned value is <em>NOT</em> an 19 * atomic snapshot; invocation in the absence of concurrent 20 * updates returns an accurate result, but concurrent updates that 21 * occur while the sum is being calculated might not be 22 * incorporated. 23 * 24 * @return the sum 25 */ 26 public long sum() { 27 Cell[] as = cells; Cell a; 28 long sum = base; 29 if (as != null) { 30 for (int i = 0; i < as.length; ++i) { 31 if ((a = as[i]) != null) 32 sum += a.value; 33 } 34 } 35 return sum; 36 }
LongAddr继承了Striped64,引入了base,cells。
longAccumulate方法:
32行:如果hash为0,强行实例化一个线程得到hash
41行:如果hash对应的cell[]坐标为空,则新建一个Cell
73行:如果cell不够,则扩容cell[]
最终将线程hash对应的cell上cas加detla
1 /** 2 * Table of cells. When non-null, size is a power of 2. 3 */ 4 transient volatile Cell[] cells; 5 6 /** 7 * Base value, used mainly when there is no contention, but also as 8 * a fallback during table initialization races. Updated via CAS. 9 */ 10 transient volatile long base; 11 12 /** 13 * Spinlock (locked via CAS) used when resizing and/or creating Cells. 14 */ 15 transient volatile int cellsBusy; 16 17 /** 18 * Handles cases of updates involving initialization, resizing, 19 * creating new Cells, and/or contention. See above for 20 * explanation. This method suffers the usual non-modularity 21 * problems of optimistic retry code, relying on rechecked sets of 22 * reads. 23 * 24 * @param x the value 25 * @param fn the update function, or null for add (this convention 26 * avoids the need for an extra field or function in LongAdder). 27 * @param wasUncontended false if CAS failed before call 28 */ 29 final void longAccumulate(long x, LongBinaryOperator fn, 30 boolean wasUncontended) { 31 int h; 32 if ((h = getProbe()) == 0) { 33 ThreadLocalRandom.current(); // force initialization 34 h = getProbe(); 35 wasUncontended = true; 36 } 37 boolean collide = false; // True if last slot nonempty 38 for (;;) { 39 Cell[] as; Cell a; int n; long v; 40 if ((as = cells) != null && (n = as.length) > 0) { 41 if ((a = as[(n - 1) & h]) == null) { 42 if (cellsBusy == 0) { // Try to attach new Cell 43 Cell r = new Cell(x); // Optimistically create 44 if (cellsBusy == 0 && casCellsBusy()) { 45 boolean created = false; 46 try { // Recheck under lock 47 Cell[] rs; int m, j; 48 if ((rs = cells) != null && 49 (m = rs.length) > 0 && 50 rs[j = (m - 1) & h] == null) { 51 rs[j] = r; 52 created = true; 53 } 54 } finally { 55 cellsBusy = 0; 56 } 57 if (created) 58 break; 59 continue; // Slot is now non-empty 60 } 61 } 62 collide = false; 63 } 64 else if (!wasUncontended) // CAS already known to fail 65 wasUncontended = true; // Continue after rehash 66 else if (a.cas(v = a.value, ((fn == null) ? v + x : 67 fn.applyAsLong(v, x)))) 68 break; 69 else if (n >= NCPU || cells != as) 70 collide = false; // At max size or stale 71 else if (!collide) 72 collide = true; 73 else if (cellsBusy == 0 && casCellsBusy()) { 74 try { 75 if (cells == as) { // Expand table unless stale 76 Cell[] rs = new Cell[n << 1]; 77 for (int i = 0; i < n; ++i) 78 rs[i] = as[i]; 79 cells = rs; 80 } 81 } finally { 82 cellsBusy = 0; 83 } 84 collide = false; 85 continue; // Retry with expanded table 86 } 87 h = advanceProbe(h); 88 } 89 else if (cellsBusy == 0 && cells == as && casCellsBusy()) { 90 boolean init = false; 91 try { // Initialize table 92 if (cells == as) { 93 Cell[] rs = new Cell[2]; 94 rs[h & 1] = new Cell(x); 95 cells = rs; 96 init = true; 97 } 98 } finally { 99 cellsBusy = 0; 100 } 101 if (init) 102 break; 103 } 104 else if (casBase(v = base, ((fn == null) ? v + x : 105 fn.applyAsLong(v, x)))) 106 break; // Fall back on using base 107 } 108 }
posted on 2021-08-25 21:12 Iversonstear 阅读(391) 评论(0) 编辑 收藏 举报