02. 동시 멀티 스레딩 -CAS

CAS 무엇입니까

  • 이름 : 비교 및 ​​의미 세트, 설정하기 전에 그들을 비교
  • 라고도 : 비교 및 ​​스왑, 비교 및 ​​교환하기

방법

영상

적용 예 및 일방 (JUC) 아래

AtomicInteger 소스 패킷 분석 CAS 원자의 카테고리하에 수행
    //创建一个原子类的对象,并进行++ 操作,从0开始
   AtomicInteger atomicInteger = new AtomicInteger();
    atomicInteger.incrementAndGet();
复制代码
  • 수행 내부 운영 봐
  • 내부 구조 AtomicInteger
public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;
    
    // CAS的核心类,由于java方法无法直接访问底层系统,需要通过本地(native)方法来访问
    //Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    //表示该变量值在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的
    //偏移量的理解:内存中存数数据的方式:一个存储数据的 实际地址=段首地址+偏移量 
    //对应的现实中  家庭地址= 小区地址+门牌号
    private static final long valueOffset;
    //使用volatile保证了多线程之间的内存可见性
    private volatile int value;
    
    
       // 创建对象的时候  就会将valueOffset的值获取到  
    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
    
 
    
    
     /**
     * 有参构造
     */
    public AtomicInteger(int initialValue) {
        value = initialValue;
    }

    /**
     * 无参构造
     */
    public AtomicInteger() {
    }

复制代码
  • 어떻게 각각의 증가가 자성이 있는지 확인하기
    /**
     *
     */
    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }
    
    
           
       
       /**
         *
         * @param var1 : AtomicInteger对象
         * @param var2 : valueOffset
         * @param var4 : 固定值 1
         * @Description :将value进行自增,并且返回自增的值
         * @Author Licy
         * @Date  2019/6/14 20:03
         * @return int
         *
         */
    
    public final int getAndAddInt(Object var1, long var2, int var4) {
     int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
     
        //可以看成compareAndSwapInt(obj, offset, expect, update)
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}
复制代码
특정 프로세스
  • 실제 값은 값에 저장된
  • 클래스가로드 될 때, 또한 안전하지 않은 예를 인수
  • valueOffset을 정의하고, 상기 취득 된 오프셋 값이 될 때 실행 코드, 정적 코드 블록의 정적 블록의 변화 값을 초기화
  • getAndAddInt 함수 수득 VAR5 valueOffset 나타내는 특정 값, 즉 값 값인
  • compareAndSwapInt 기능을 의미 :이 단계가 성공하지 못한 경우 (OBJ)의 값과 너무 오래 그가 업데이 트를 업데이트로, 다른 스레드가이 변수를 변경 없다는 것을 증명 동일한 기대한다면, CAS는, 스핀 모드 동작의 다음 사용은 CAS를 계속
  • 두 단계와의 비교는 CPU의 JNI 지시 완성 문제 원자되도록하는 수단으로, 사실, 언뜻 배치

기본이되는 원칙

  • CAS 기본 사용 JNI는 핫스팟 소스 코드가있는 경우, 당신이에 Unsafe.cpp 구현 찾을 수 달성하기 위해 C 코드를 호출합니다 :

발생하는 문제

1, ABA 문제
  • CAS 검사를 사용하는 경우 CAS가 업데이트되어 변경되지 않은 경우 변경되지 않은 운영 값을 낮은 값을 확인하는 시간이 필요하지만, 값이 인 경우, A B되었다, 그는 A는, 당신은 그것을 찾을 수 있습니다 값은 변경할 수 있지만, 실제로는 변경되지 않습니다. 이것은 ABA 문제 CAS입니다
  • 일반적인 솔루션은 버전 번호입니다. 버전 번호의 버전 번호를 더한 다음, 그 ABA 1A-2B-3A 될 때마다 변수가 업데이트 될 때, 변수의 앞에 추가된다.
  • 현재 그것은 ABA 문제를 해결하기 위해 원자 패키지 JDK의 클래스 AtomicStampedReference를 제공합니다. 이 클래스의 작업에있어서의 compareAndSet 현재 참조 예상 기준 같으며 모든 동일한 원자 기준 값 및 상기 플래그가 지정된 갱신 값으로 설정된 경우 현재 마크가 예상 플래그와 같은지 검사한다.
2, 사이클 시간은 큰 지출
  • CAS가 실패하면 긴 스핀이 매우 큰 CPU의 실행 비용을 가져올 경우, 장소에 회전합니다.
  • 그러나이 문제는 jdk8에서 해결되었습니다

자바 (8)는 CAS의 메커니즘을 최적화하기

java.util.concurrency.atomic.LongAdder
  • java8 새로운 클래스, 메소드 원자 통합 값
일반 과정

영상

public class LongAdder extends Striped64 implements Serializable {
//.....
}


abstract class Striped64 extends Number {


    /**
     * 
     */
    transient volatile Cell[] cells;

    /**
     * Base value, used mainly when there is no contention, but also as
     * a fallback during table initialization races. Updated via CAS.
     */
    transient volatile long base;


//
@sun.misc.Contended static final class Cell {
        volatile long value;
        Cell(long x) { value = x; }
        final boolean cas(long cmp, long val) {
            return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
        }

        // Unsafe mechanics
        private static final sun.misc.Unsafe UNSAFE;
        private static final long valueOffset;
        static {
            try {
                UNSAFE = sun.misc.Unsafe.getUnsafe();
                Class<?> ak = Cell.class;
                valueOffset = UNSAFE.objectFieldOffset
                    (ak.getDeclaredField("value"));
            } catch (Exception e) {
                throw new Error(e);
            }
        }
    }
}
复制代码
  • LongAdder 클래스는 Striped64을 상속
  • Striped64 클래스는 데이터 지연로드 셀 [] 및 추가베이스 인스턴스 필드를 유지
  • 데이터 크기는 N (2)의 전력은 내부 액세스 스레드의 해시 값을 이용하여 각각의 스레드
  • 사용 통계 경합 변형 의사 셀 데이터 공유의 발생을 방지하기 위해, 다른 운영 체제 자리 캐시 라인 샘플 크기를 해결하기 위해, 거짓 공유 (거짓 공유 월호)의 경우를 해결하는 것으로
public class LongAdder extends Striped64 implements Serializable {
    private static final long serialVersionUID = 7249069246863182397L;
    public LongAdder() {
    }

 
     
public void add(long x) {
    // as是Striped64中的cells属性
    // b是Striped64中的base属性
    // v是当前线程hash到的Cell中存储的值
    // m是cells的长度减1,hash时作为掩码使用
    // a是当前线程hash到的Cell
    Cell[] as; long b, v; int m; Cell a;
    // 条件1:cells不为空,说明出现过竞争,cells已经创建
    // 条件2:cas操作base失败,说明其它线程先一步修改了base,正在出现竞争
    if ((as = cells) != null || !casBase(b = base, b + x)) {
        // true表示当前竞争还不激烈
        // false表示竞争激烈,多个线程hash到同一个Cell,可能要扩容
        boolean uncontended = true;
        // 条件1:cells为空,说明正在出现竞争,上面是从条件2过来的
        // 条件2:应该不会出现
        // 条件3:当前线程所在的Cell为空,说明当前线程还没有更新过Cell,应初始化一个Cell
        // 条件4:更新当前线程所在的Cell失败,说明现在竞争很激烈,多个线程hash到了同一个Cell,应扩容
        if (as == null || (m = as.length - 1) < 0 ||
            // getProbe()方法返回的是线程中的threadLocalRandomProbe字段
            // 它是通过随机数生成的一个值,对于一个确定的线程这个值是固定的
            // 除非刻意修改它
            (a = as[getProbe() & m]) == null ||
            !(uncontended = a.cas(v = a.value, v + x)))
            // 调用Striped64中的方法处理
            longAccumulate(x, null, uncontended);
    }
}


    /**
     * Equivalent to {@code add(1)}.
     */
    public void increment() {
        add(1L);
    }

    /**
     * Equivalent to {@code add(-1)}.
     */
    public void decrement() {
        add(-1L);
    }
复制代码
final void longAccumulate(long x, LongBinaryOperator fn,
                              boolean wasUncontended) {
    // 存储线程的probe值
    int h;
    // 如果getProbe()方法返回0,说明随机数未初始化
    if ((h = getProbe()) == 0) {
        // 强制初始化
        ThreadLocalRandom.current(); // force initialization
        // 重新获取probe值
        h = getProbe();
        // 都未初始化,肯定还不存在竞争激烈
        wasUncontended = true;
    }
    // 是否发生碰撞
    boolean collide = false;                // True if last slot nonempty
    for (;;) {
        Cell[] as; Cell a; int n; long v;
        // cells已经初始化过
        if ((as = cells) != null && (n = as.length) > 0) {
            // 当前线程所在的Cell未初始化
            if ((a = as[(n - 1) & h]) == null) {
                // 当前无其它线程在创建或扩容cells,也没有线程在创建Cell
                if (cellsBusy == 0) {       // Try to attach new Cell
                    // 新建一个Cell,值为当前需要增加的值
                    Cell r = new Cell(x);   // Optimistically create
                    // 再次检测cellsBusy,并尝试更新它为1
                    // 相当于当前线程加锁
                    if (cellsBusy == 0 && casCellsBusy()) {
                        // 是否创建成功
                        boolean created = false;
                        try {               // Recheck under lock
                            Cell[] rs; int m, j;
                            // 重新获取cells,并找到当前线程hash到cells数组中的位置
                            // 这里一定要重新获取cells,因为as并不在锁定范围内
                            // 有可能已经扩容了,这里要重新获取
                            if ((rs = cells) != null &&
                                (m = rs.length) > 0 &&
                                rs[j = (m - 1) & h] == null) {
                                // 把上面新建的Cell放在cells的j位置处
                                rs[j] = r;
                                // 创建成功
                                created = true;
                            }
                        } finally {
                            // 相当于释放锁
                            cellsBusy = 0;
                        }
                        // 创建成功了就返回
                        // 值已经放在新建的Cell里面了
                        if (created)
                            break;
                        continue;           // Slot is now non-empty
                    }
                }
                // 标记当前未出现冲突
                collide = false;
            }
            // 当前线程所在的Cell不为空,且更新失败了
            // 这里简单地设为true,相当于简单地自旋一次
            // 通过下面的语句修改线程的probe再重新尝试
            else if (!wasUncontended)       // CAS already known to fail
                wasUncontended = true;      // Continue after rehash
            // 再次尝试CAS更新当前线程所在Cell的值,如果成功了就返回
            else if (a.cas(v = a.value, ((fn == null) ? v + x :
                                         fn.applyAsLong(v, x))))
                break;
            // 如果cells数组的长度达到了CPU核心数,或者cells扩容了
            // 设置collide为false并通过下面的语句修改线程的probe再重新尝试
            else if (n >= NCPU || cells != as)
                collide = false;            // At max size or stale
            // 上上个elseif都更新失败了,且上个条件不成立,说明出现冲突了
            else if (!collide)
                collide = true;
            // 明确出现冲突了,尝试占有锁,并扩容
            else if (cellsBusy == 0 && casCellsBusy()) {
                try {
                    // 检查是否有其它线程已经扩容过了
                    if (cells == as) {      // Expand table unless stale
                        // 新数组为原数组的两倍
                        Cell[] rs = new Cell[n << 1];
                        // 把旧数组元素拷贝到新数组中
                        for (int i = 0; i < n; ++i)
                            rs[i] = as[i];
                        // 重新赋值cells为新数组
                        cells = rs;
                    }
                } finally {
                    // 释放锁
                    cellsBusy = 0;
                }
                // 已解决冲突
                collide = false;
                // 使用扩容后的新数组重新尝试
                continue;                   // Retry with expanded table
            }
            // 更新失败或者达到了CPU核心数,重新生成probe,并重试
            h = advanceProbe(h);
        }
        // 未初始化过cells数组,尝试占有锁并初始化cells数组
        else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
            // 是否初始化成功
            boolean init = false;
            try {                           // Initialize table
                // 检测是否有其它线程初始化过
                if (cells == as) {
                    // 新建一个大小为2的Cell数组
                    Cell[] rs = new Cell[2];
                    // 找到当前线程hash到数组中的位置并创建其对应的Cell
                    rs[h & 1] = new Cell(x);
                    // 赋值给cells数组
                    cells = rs;
                    // 初始化成功
                    init = true;
                }
            } finally {
                // 释放锁
                cellsBusy = 0;
            }
            // 初始化成功直接返回
            // 因为增加的值已经同时创建到Cell中了
            if (init)
                break;
        }
        // 如果有其它线程在初始化cells数组中,就尝试更新base
        // 如果成功了就返回
        else if (casBase(v = base, ((fn == null) ? v + x :
                                    fn.applyAsLong(v, x))))
            break;                          // Fall back on using base
    }
}

复制代码
  • 먼저베이스의 값은 멀티 스레딩을 시작하여 누적 값을 유지하는, 거기의베이스에 축적되어
  • 예를 들어, 불과 5되었다 축적하기 시작하고 동시 업데이트, 우리는 하위 CAS 메커니즘의 구현을 시작합니다 스레드의 과도한 번호를 발견
  • CSA 분할기구는 셀 어레이 내에 각각 배열 축적 CAS 용 셀의 내부 값을 다른 값으로 데이터의 세그먼트,이 방법은 각각의 스레드 다수이며, CAS 계산 압력 씌워 분산액 동일한 값으로 갱신 할 때 동시에 발생하는 셀의 값, 보상 스레딩 무한 루프의 상이한 세그먼트
  • 이 클래스는 또한 구현 자동 분할 마이그레이션 내부 메커니즘, 즉 셀의 값은 CAS가 자동으로 다른 세포 CAS 연산 될 값 세그먼트 값을 찾을 것이다 실패 실행되는 경우
  • 당신은 현재 누적 총을 얻을 LongAdder 원하고, 당신에게 다시 값을 추가 모든 세그먼트의 셀에 대한 기본 값이됩니다.

추천

출처juejin.im/post/5d1c84baf265da1bc7525555