話說CAS

1、前言

  1. cas 通常認爲是compare and swap 也能夠認爲是compare and set java

  2. cas涉及三個值
  3. (1) P  變量內存地址 
    (2)E  指望值 ,CPU作計算以前拿出來的舊值
      (3)   X 須要設置的新值

    原子操做爲: 拿出內存地址當前的值A ,比較A == E ? 是 : 設置P內存的值爲X 否:結束。。失敗面試

  4. (1) 第一篇 話說synchronized 畫過CAS的流程圖 我們再來一張?緩存

    話說CAS

    (2) CAS面試常常問的一個是ABA 問題 什麼是ABA ? 上圖
    話說CAS安全

    (3) 有人說ABA 不影響啊 我反正指望的值是A 你最後是A就得了唄 微信

​ 這個還要看具體的業務,拿生活中例子來講,銀行職員小孫,偷拿了銀行100萬,ide

而後去投資賺了20萬,最後把100萬還回去。 你細品。。 銀行能容許嗎oop

(4)ABA 的解決方案 版本 version 怎麼解決 ? this

話說CAS

2、DEMO

1. CAS 簡單使用

假若有一個值 int count ,2個線程 每一個線程給count加5000次 1
按道理說 每一個人給你5000 你應該有1萬塊 atom

public class CasTest {
    public  static int count = 0;
    public static void main(String[] args) throws InterruptedException {
        new Sub("第一個").start();
        new Sub("第二個").start();

        TimeUnit.SECONDS.sleep(5);
        System.out.println("count="+CasTest.count);
    }
}

class Sub extends  Thread{
    private String name;
    public Sub(String name) {
        this.name = name;
    }
    public void run() {
        System.out.println(name+"開始+");
        for (int i = 0; i < 5000; i++) {
            try {
                CasTest.count = CasTest.count+1;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        System.out.println(name+"加完了..");
    }
}

執行結果:
第一個開始+
第二個開始+
第一個加完了..
第二個加完了..
count=6811

解決方案1 加鎖synchronized 或者 lock 均可以 spa

public class CasTest02 {
    public  static Integer count = 0;
    public static void main(String[] args) throws InterruptedException {
        new Sub02("第一個").start();
        new Sub02("第二個").start();

        TimeUnit.SECONDS.sleep(5);
        System.out.println("count="+CasTest02.count);
    }
}

class Sub02 extends  Thread{
    private String name;
    public Sub02(String name) {
        this.name = name;
    }
    public void run() {
        System.out.println(name+"開始+");
        for (int i = 0; i < 500; i++) {
            try {
                // 加鎖
                synchronized (CasTest02.class) {
                    CasTest02.count = CasTest02.count+1;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        System.out.println(name+"加完了..");
    }
}

解決方案2: CAS java自帶的原子類 AtomicInteger

​ 讀者讀到這裏能夠了解一下LongAdder

public class CasTest03 {
    public  static AtomicInteger count = new AtomicInteger(0);
    public static void main(String[] args) throws InterruptedException {
        new Sub03("第一個").start();
        new Sub03("第二個").start();

        TimeUnit.SECONDS.sleep(5);
        System.out.println("count="+CasTest03.count);
    }
}

class Sub03 extends  Thread{
    private String name;
    public Sub03(String name) {
        this.name = name;
    }
    public void run() {
        System.out.println(name+"開始+");
        for (int i = 0; i < 5000; i++) {
            // 加鎖
            CasTest03.count.incrementAndGet();
        }
        System.out.println(name+"加完了..");
    }
}
執行結果:
第一個開始+
第二個開始+
第二個加完了..
第一個加完了..
count=10000

2. ABA 問題

這裏簡單復現一個ABA問題 可能不是很精確 讀者朋友體會意思便可

public class CasTest04 {
    public  static AtomicInteger count = new AtomicInteger(0);
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{

            count.compareAndSet(0,1);
            count.compareAndSet(1,0);
            System.out.println("線程1 把count從0 修改成1  再從1  修改成0  ");
        },"線程1").start();

        new Thread(()->{
           try {
               TimeUnit.SECONDS.sleep(1);
               // 這裏是0  可是已經不是他所但願的那個0 了
               count.compareAndSet(0,4);
               System.out.println("線程2 把count 從0 修改成4");
           } catch (Exception e) {
               e.printStackTrace();
           }
        },"線程2").start();

        TimeUnit.SECONDS.sleep(5);
        System.out.println("count="+count);
    }
}
執行結果:
線程1 把count從0 修改成1  再從1  修改成0  
線程2 把count 從0 修改成4
count=4

ABA解決 加版本

public class CasTest05 {
    public  static AtomicStampedReference<Integer> count = new AtomicStampedReference<>(0,0);
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            try {
                // 等1秒  讓線程2拿到版本
                TimeUnit.SECONDS.sleep(1);
                boolean res = count.compareAndSet(
                    0, 
                    1, 
                    count.getStamp(), 
                    count.getStamp() + 1);
                boolean res2 = count.compareAndSet
                    (1, 
                     0, 
                     count.getStamp(), 
                     count.getStamp() + 1);
                System.out.println("線程1 把count從0 修改成1  再從1  修改成0  "
                                   + ( res2 ? "成功!":"失敗!"));
            }catch (Exception r){
                r.printStackTrace();
            }

        },"線程1").start();

        new Thread(()->{
           try {
               // 版本 
               int stamp = count.getStamp();
               TimeUnit.SECONDS.sleep(2);
               // 這裏是0  可是已經不是他所但願的那個0 了  版本已經變了 
               boolean res = count.compareAndSet(0,4,stamp,stamp+1);
               System.out.println("線程2 把count 從0 修改成4" 
                                  + ( res ? "  成功!":"  失敗!"));
           } catch (Exception e) {
               e.printStackTrace();
           }
        },"線程2").start();

        TimeUnit.SECONDS.sleep(5);
        System.out.println("count="+count.getReference());
    }
}

3、 僞裝學術討論

/**
 * @author 木子的晝夜
 */
public class CasTest {
    public static void main(String[] args) {
        // JUC包裏的原子類  線程安全的 
        AtomicInteger ai = new AtomicInteger();
        // 加1 並返回 
        Integer res = ai.incrementAndGet();
        System.out.println(res);

        // 上邊這句話的意思至關於 
        int i = 0;
        i = i+1;
        int resi = i;
        System.out.println(resi);
    }
}

 /**
     * Atomically increments by one the current value.
     * 當前值自動加1 
     * @return the updated value 
     * 返回更新了的值
     */
    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

    // Unsafe.java#getAndAddInt
    public final int getAndAddInt(Object obj, long offset, int step) {
        int E;
        do {
            var5 = this.getIntVolatile(obj, offset);
            // 這裏不必定能一次就成功哦  會執行屢次 
            // 跟一個姑娘表白 一次不成 你就灰溜溜走了 ? 活該單身..
        } while(!this.compareAndSwapInt(obj, offset, E, E + step));

        return var5;
    }
    // Unsafe.java#getIntVolatile 這個方法是獲 對象obj 內存開始地址 相對偏移位置offset 對應的值
    // 說白了 就是獲取對象對應字段的值 
    public native int getIntVolatile(Object obj, long offset);

    // 
    public final native boolean compareAndSwapInt(
        Object obj,  // 被修改屬性的對象 
        long offset, // 被修改字段相對於當前對象內存首地址偏移量 能夠經過他直接去內存拿數據
        int E, // 指望值 
        int X);// 須要設置的新值
// obj設置屬性的對象 offset 字段相對於類內存起始位置偏移量  e指望值  x要設置的值 
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSetInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x)) {
  // 轉換對象格式 jobject- > oop 
  oop p = JNIHandles::resolve(obj);
  if (p == NULL) {
     // 獲取filed對應的內存地址 對象起始地址+偏移量
    volatile jint* addr = (volatile jint*)index_oop_from_field_offset_long(p, offset);
    // 把addr內存對應的值 設置爲x 前提是內存值要等於e 
    return RawAccess<>::atomic_cmpxchg(addr, e, x) == e;
  } else {
    // 
    assert_field_offset_sane(p, offset);
    return HeapAccess<>::atomic_cmpxchg_at(p, (ptrdiff_t)offset, e, x) == e;
  }
} UNSAFE_END
// 這裏就調用了 atomic_cmpxchg: 系統方法 : 原子比較並交換計數值。
atomic_cmpxchg(void* addr, T compare_value, T new_value) {
    if (is_hardwired_primitive<decorators>()) {
        const DecoratorSet expanded_decorators = decorators | AS_RAW;
        return PreRuntimeDispatch::atomic_cmpxchg<expanded_decorators>
            (addr, compare_value, new_value);
    } else {
        return RuntimeDispatch<decorators, T, BARRIER_ATOMIC_CMPXCHG>::atomic_cmpxchg
            (addr, compare_value, new_value);
      }
}

再底層就是系統級別的實現了,CPU 實現Atomic ,我也是看文章看得,說是有2中方式

  1. 使用總線鎖 總線就是老大 CPU小c 給總線發一個LOCK信號 總線收到以後 小c就獨佔共享內存了,其餘CPU
    就沒有使用權限了 ,數據誇緩存行時使用總線鎖 這時候不能用緩存鎖
  2. 使用緩存鎖 大多數時候 咱們只須要保證對某一塊內存的操做時原子性便可,緩存鎖就是若是內存區域被緩存再處理器的緩存行中,而且操做的時候緩存行被鎖定了,那麼當處理器計算完回寫到內存時,處理器就把緩存行的地址修改了,若是這時有兩一個處理器回寫數據到緩存行,咦? 失效了。。

下邊鏈接是我準備寫文章的目錄,若是有讀者以爲須要添加,請微信公衆直接發消息給我。

https://www.processon.com/view/link/5f57817963768959e2dc7dca

最後附上本身公衆號剛開始寫 願一塊兒進步:

話說CAS

注意: 以上文字 僅表明我的觀點,僅供參考,若有問題還請指出,當即立刻連滾帶爬的從被窩裏出來改正。

相關文章
相關標籤/搜索