二:CAS的理解

CAS 是什麼

  • CAS 的全稱 Compare-And-Swap,它是一條 CPU 併發操作。

  • 它的功能是判斷內存某一個位置的值是否爲預期,如果是則更改這個值,這個過程就是原子的。

  • CAS 併發原體現在 JAVA 語言中就是 sun.misc.Unsafe 類中的各個方法。調用 UnSafe 類中的 CAS 方法,JVM 會幫我們實現出 CAS 彙編指令。這是一種完全依賴硬件的功能,通過它實現了原子操作。由於 CAS 是一種系統源語,源語屬於操作系統用語範疇,是由若干條指令組成,用於完成某一個功能的過程,並且原語的執行必須是連續的,在執行的過程中不允許被中斷,也就是說 CAS 是一條原子指令,不會造成所謂的數據不一致的問題。

  • 分析一下 getAndAddInt() 這個方法

    // unsafe.getAndAddInt
    public final int getAndAddInt(Object obj, long valueOffset, long expected, int val) {
        int temp;
        do {
            temp = this.getIntVolatile(obj, valueOffset);  // 獲取快照值
        } while (!this.compareAndSwap(obj, valueOffset, temp, temp + val));  // 如果此時 temp 沒有被修改,就能退出循環,否則重新獲取
        return temp;
    }
    

CAS 的缺點?

  • 循環時間長開銷很大
    • 如果 CAS 失敗,會一直嘗試,如果 CAS 長時間一直不成功,可能會給 CPU 帶來很大的開銷(比如線程數很多,每次比較都是失敗,就會一直循環),所以希望是線程數比較小的場景。
  • 只能保證一個共享變量的原子操作
    • 對於多個共享變量操作時,循環 CAS 就無法保證操作的原子性。
  • 引出 ABA 問題

CAS 底層原理?談談對 UnSafe 的理解?

public class CASDemo {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(666);
        // 獲取真實值,並替換爲相應的值
        boolean b = atomicInteger.compareAndSet(666, 2019);
        System.out.println(b); // true
        boolean b1 = atomicInteger.compareAndSet(666, 2020);
        System.out.println(b1); // false
        atomicInteger.getAndIncrement();
    }
}

getAndIncrement()方法

/**
 * Atomically increments by one the current value.
 *
 * @return the previous value
 */
public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}

UnSafe 類是什麼?

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            // 獲取下面 value 的地址偏移量
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;
	// ...
}
  • Unsafe 是 CAS 的核心類,由於 Java 方法無法直接訪問底層系統,而需要通過本地(native)方法來訪問, Unsafe 類相當一個後門,基於該類可以直接操作特定內存的數據。Unsafe 類存在於 sun.misc 包中,其內部方法操作可以像 C 指針一樣直接操作內存,因爲 Java 中 CAS 操作執行依賴於 Unsafe 類。
  • 變量 vauleOffset,表示該變量值在內存中的偏移量,因爲 Unsafe 就是根據內存偏移量來獲取數據的。
  • 變量 value 用 volatile 修飾,保證了多線程之間的內存可見性。

CAS的ABA問題

  • ABA 問題是怎麼產生的
public class ABADemo {
    private static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);

    public static void main(String[] args) {
        new Thread(() -> {
            atomicReference.compareAndSet(100, 101);
            atomicReference.compareAndSet(101, 100);
        }).start();

        new Thread(() -> {
            // 保證上面線程先執行
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicReference.compareAndSet(100, 2019);
            System.out.println(atomicReference.get()); // 2019
        }).start();
    }
}
  • 原子更新引用
// 原子引用
public class AtomicReferenceDemo {
    public static void main(String[] args) {
        User cuzz = new User("cuzz", 18);
        User faker = new User("faker", 20);
        AtomicReference<User> atomicReference = new AtomicReference<>();
        atomicReference.set(cuzz);
        System.out.println(atomicReference.compareAndSet(cuzz, faker)); // true
        System.out.println(atomicReference.get()); // User(userName=faker, age=20)
    }
}
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;

/**
 * 時間戳原子引用
 **/

public class ABADemo2 {
    private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);

    public static void main(String[] args) {
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + " 的版本號爲:" + stamp);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1 );
            atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1 );
        }).start();

        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + " 的版本號爲:" + stamp);
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean b = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);
            System.out.println(b); // false
            System.out.println(atomicStampedReference.getReference()); // 100
        }).start();
    }
}
我們先保證兩個線程的初始版本爲一致,後面修改是由於版本不一樣就會修改失敗。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章