面试三、多线程之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编辑  收藏  举报

导航