1. 程式人生 > 實用技巧 >JVM 判斷物件已死,實踐驗證GC回收

JVM 判斷物件已死,實踐驗證GC回收

簡介

CAS(Compare and swap)比較和替換是設計併發演算法時用到的一種技術。簡單來說,比較和替換是使用一個期望值和一個變數的當前值進行比較,如果當前變數的值與我們期望的值相等,就使用一個新值替換當前變數的值。

        AtomicInteger atomicInteger = new AtomicInteger(10);
        System.out.println(atomicInteger.compareAndSet(10, 4));
        System.out.println(atomicInteger.compareAndSet(10, 2));
        System.out.println(atomicInteger);

底層原理

以AtomicInteger的getAndIncrement方法為例:

    public final int getAndIncrement() {
        //this:當前物件
        //valueOffset:記憶體地址偏移量(記憶體地址)
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }

這裡由unsafe呼叫getAndAddInt方法。

Unsafe

Unsafe是CAS的核心類,由於Java方法無法直接訪問底層系統,需要通過本地(native)方法類訪問,Unsafe相當於一個後門,基於該類可以直接操作特定記憶體的資料。Unsafe存在於sun.misc包中,其內部方法操作可以像C的指標一樣直接操作記憶體,因為java中CAS操作的執行依賴於Unsafe類的方法。

Unsafe類中的方法都可以直接呼叫作業系統底層資源執行相應任務。

變數valueOffset,表示該變數值在記憶體中的偏移地址,因為Unsafe就是根據記憶體便宜地址獲取資料的

變數用value修飾,保證了多執行緒之間的記憶體可見性。

CAS(Compare and swap),是一條CPU併發原語。它的功能是判斷記憶體某個位置的值是否為預期值,如果是則更改為新的值,這個過程是原子的。

原語的執行必須是連續的,執行過程中不允許打斷,所以CAS是一條CPU的原子指令,所以不會造成所謂的資料不一致問題。

var1:AtomicInteger物件本身

var2:該物件值的引用地址

var4:需要變動的值

var5:是用過var1 var2找出的主記憶體中真實的值

compareAndSwapInt:var2和var5比較,相同則 var5+var4,並返回true,跳出do-while迴圈。如果不同,繼續取值再比較,直至更新完成。

CAS缺點

1)如果CAS失敗,會一直嘗試,如果CAS長時間不成功,可能會給CPU帶來很大的開銷

2)只能保證一個共享變數的原子操作

3)ABA問題

ABA

如果一個執行緒1從記憶體位置V中取出A,這時候另一個執行緒2也從記憶體中取出A,並且執行緒2進行了一些操作將值程式設計了B,然後執行緒2又將V位置的資料變成A,這時候執行緒one進行CAS操作發現記憶體中仍然是A,然後執行緒1操作成功。

儘管執行緒1的CAS操作成功,但是不代表這個過程就是沒有問題的。

原子引用

AtomicReference:原子引用

 //toString,Getter,Setter省略 
class Stu{
    private String name;

    public Stu(String name) {
        this.name = name;
    }

}

public class AtomicReferenceDemo {
    public static void main(String[] args) {
        Stu zs = new Stu("zs");
        Stu ls = new Stu("ls");

        AtomicReference<Stu> reference = new AtomicReference<>(zs);

        System.out.println(reference.compareAndSet(zs,ls));
        System.out.println(reference.compareAndSet(zs,ls));
    }
}

ABA問題解決

新增一種機制,修改版本號(類似時間戳)

存在ABA問題的程式碼:

        new Thread(()->{
            atomicReference.compareAndSet(10,11);
            atomicReference.compareAndSet(11,10);
        },"t1").start();

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

使用AtomicStampedReference解決:

    private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(10,1);

    public static void main(String[] args) {
        new Thread(()->{
            int stamp = atomicStampedReference.getStamp();
            System.out.println("t3 first stamp :"+stamp);
            atomicStampedReference.compareAndSet(10,11,1,2);
            atomicStampedReference.compareAndSet(11,10,2,3);
        },"t3").start();

        new Thread(()->{
            try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}
            System.out.println(atomicStampedReference.compareAndSet(10, 11, 1, 2));
        },"t4").start();
    }