java面試-CAS底層原理


一、CAS是什么?

比較並交換,它是一條CPU並發原語判斷內存某個位置的值是否為預期值,如果是更改為新值,這個過程是原子的。

原語屬於操作系統用語范疇,是由若干條指令組成的,用於完成某個功能的一個過程,並且原語的執行必須是連續的,在執行過程中不允許被中斷,也就是說CAS是一條CPU的原子指令,不會造成所謂的數據不一致問題

public class CASDemo {

    public static void main(String[] args) {
        //主物理內存的值默認是5
        AtomicInteger atomicInteger = new AtomicInteger(5);
        //如果線程的期望值與物理內存的真實值一樣,就修改為更新值。
        System.out.println(atomicInteger.compareAndSet(5,2019)+" current data:"+atomicInteger.get()); //t1線程的工作內存 變量的副本拷貝
        System.out.println(atomicInteger.compareAndSet(5,1024)+" current data:"+atomicInteger.get()); //t2線程的工作內存 變量的副本拷貝
    } 
}

二、說說CAS底層原理?談談你對UnSafe的理解

要點:Unsafe類(存在rt.jar中)+CAS自旋鎖

  • AtomicInteger的源碼
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 {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;
}

1、Unsafe類

是CAS的核心類,由於java方法無法直接訪問底層系統,需要通過本地(native)方法來訪問,Unsafe相當於一個后門,基於該類可以直接操作特定內存的數據

Unsafe類存在於sun.misc包中,其內部方法操作可以像C的指針一樣直接操作內存,因為Java中CAS操作執行依賴於Unsafe類

Unsafe類中的所有方法都是native修飾的,也就是說Unsafe類中的方法都直接調用操作系統底層資源執行相應任務  

2、變量valueOffset,表示該變量在內存中的偏移地址,因為Unsafe就是根據內存偏移地址獲取數據的。

//this:當前對象
//valueOffset:內存偏移量 
 public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
 }
//var1 AtomicInteger對象本身
//var2 該對象值的引用地址
//var4 需要變動的值
//var5 用var1 var2找出主內存中真實的值
//用該對象當前值與var5比較,如果相同,更新var5+var4返回true,如果不同,繼續取值比較,直到更新完成
public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}  

總結:getAndIncrement()底層調用unsafe類方法,傳入三個參數,unsafe.getAndAddInt() 底層使用CAS思想,如果比較成功加1,如果比較失敗重新獲得,再比較一次,直至成功。

3、變量value用volatile修飾,保證了多線程之間的內存可見性。

三、CAS缺點:

  • 循環時間長開銷大(如果CAS失敗,會一直嘗試)
  • 只能保證一個共享變量的原子操作。(對多個共享變量操作時,循環CAS無法保證操作的原子性,只能用加鎖來保證)
  • 存在ABA問題

四、原子類AtomicInteger類ABA問題及解決方案

1、ABA問題是怎么產生的?

當第一個線程執行CAS(V,E,U)操作,在獲取到當前變量V,准備修改為新值U前,另外兩個線程已連續修改了兩次變量V的值,使得該值又恢復為舊值,這樣我們就無法正確判斷這個變量是否已被修改過。

2、ABA問題的解決方案:

AtomicStampedReference:是一個 帶有時間戳的對象引用,在每次修改后,不僅會設置新值還會記錄更改的時間。
AtomicMarkableReference:維護的是一個boolean值的標識,這種方式並不能完全防止ABA問題的發生,只能減少ABA發生的概率。
public class ABADemo {
    static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
    static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);


    public static void main(String[] args) throws InterruptedException {
        System.out.println("========ABA問題的產生=========");
        new Thread(() -> {
            atomicReference.compareAndSet(100, 101);
            atomicReference.compareAndSet(101, 100);

        }, "t1").start();

        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(atomicReference.compareAndSet(100, 2019) + " " + atomicReference.get());
        }, "t2").start();
        TimeUnit.SECONDS.sleep(2);

        System.out.println("========ABA問題的解決=========");

        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "線程第1次版本號:" + stamp);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "線程第2次版本號:" + atomicStampedReference.getStamp());
            atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "線程第3次版本號:" + atomicStampedReference.getStamp());

        }, "t3").start();

        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "線程第1次版本號:" + stamp);
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);
            System.out.println(Thread.currentThread().getName() + "修改成功否:" + result + "  當前最新版本號:" + atomicStampedReference.getStamp());
            System.out.println(Thread.currentThread().getName() + "當前最新值:" + atomicStampedReference.getReference());

        }, "t4").start();
    }
}

五、原子更新引用

public class AtomicReferenceDemo {

    public static void main(String[] args) {
        AtomicReference<User> atomicReference = new AtomicReference<>();

        User user = new User("monster", 18);
        User updateUser = new User("jack", 25);

        atomicReference.set(user);
        atomicReference.compareAndSet(user, updateUser);

        System.out.println(atomicReference.get());
    }
}

@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
class User {
    private String name;
    private int age;
}  

 

  沒有英漢互譯結果
   請嘗試網頁搜索


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM