Unsafe中CAS的實現

前言

Unsafe 是位於 sun.misc 包下的一個類。Unsafe 提供的 API 大體可分爲內存操做、CAS、Class 相關、對象操做、線程調度、系統信息獲取、內存屏障、數組操做等幾類。因爲併發相關的源碼不少用到了 CAS,好比 java.util.concurrent.atomic 相關類、AQS、CurrentHashMap 等相關類。因此本文主要講 Unsafe 中 CAS 的實現。筆者源碼環境爲 OpenJDK8java

CAS 相關

主要相關源碼算法

/**
     * 參數說明
     * @param o             包含要修改field的對象
     * @param offset        對象中某個參數field的偏移量,該偏移量不會改變
     * @param expected      指望該偏移量對應的field值
     * @param x             更新值
     * @return              true|false
     */
    public final native boolean compareAndSwapObject(Object o, long offset,
                                                     Object expected,
                                                     Object x);

    public final native boolean compareAndSwapInt(Object o, long offset,
                                                  int expected,
                                                  int x);

    public final native boolean compareAndSwapLong(Object o, long offset,
                                                   long expected,
                                                   long x);

CAS 是實現併發算法時經常使用到的一種技術。CAS 操做包含三個操做數——內存位置、預期原值及新值。執行 CAS 操做的時候,將內存位置的值與預期原值比較,若是相匹配,那麼處理器會自動將該位置值更新爲新值,不然,處理器不作任何操做。咱們都知道,CAS 是一條 CPU 的 原子指令(cmpxchg 指令),不會形成所謂的數據不一致問題,Unsafe 提供的 CAS 方法(如 compareAndSwapXXX)底層實現即爲 CPU 指令 cmpxchg。bootstrap

說明:對象的基地址 baseAddress+valueOffset 獲得 value 的內存地址 valueAddress

Unsafe 類獲取

首先看下 Unsafe 的單例實現數組

private static final Unsafe theUnsafe = new Unsafe();
    // 註解代表須要引導類加載器
    @CallerSensitive
    public static Unsafe getUnsafe() {
        Class<?> caller = Reflection.getCallerClass();
        // 僅在引導類加載器`BootstrapClassLoader`加載時才合法
        if (!VM.isSystemDomainLoader(caller.getClassLoader()))
            throw new SecurityException("Unsafe");
        return theUnsafe;
    }

那如若想使用這個類,該如何獲取其實例?有以下兩個可行方案。安全

其一,從 getUnsafe 方法的使用限制條件出發,經過 Java 命令行命令 -Xbootclasspath/a 把調用 Unsafe 相關方法的類 A 所在 jar 包路徑追加到默認的 bootstrap 路徑中,使得 A 被引導類加載器加載,從而經過 Unsafe.getUnsafe 方法安全的獲取 Unsafe 實例。併發

java -Xbootclasspath/a: ${path}   // 其中path爲調用Unsafe相關方法的類所在jar包路徑

其二,經過反射獲取單例對象 theUnsafe。測試

@Slf4j
public class UnsafeTest {

    private static Unsafe reflectGetUnsafe() {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            return (Unsafe) field.get(null);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return null;
        }
    }

    public static void main(String[] args) {
        Unsafe unsafe = UnsafeTest.reflectGetUnsafe();
    }
}

CAS 演練

  1. 建立一個類
@Getter@Setter
public class User {
    private String name;
    private int age;
}
  1. 反射獲取 Unsafe 並測試 CAS
@Slf4j
public class UnsafeTest {

    private static Unsafe reflectGetUnsafe() {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            return (Unsafe) field.get(null);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return null;
        }
    }

    public static void main(String[] args) throws Exception{
        Unsafe unsafe = UnsafeTest.reflectGetUnsafe();
        // allocateInstance: 對象操做。繞過構造方法、初始化代碼來建立對象
        User user = (User)unsafe.allocateInstance(User.class);
        user.setName("admin");
        user.setAge(17);


        Field name = User.class.getDeclaredField("name");
        Field age = User.class.getDeclaredField("age");

        // objectFieldOffset: 返回對象成員屬性在內存地址相對於此對象的內存地址的偏移量
        long nameOffset = unsafe.objectFieldOffset(name);
        long ageOffset = unsafe.objectFieldOffset(age);

        System.out.println("name內存偏移地址:" + nameOffset);
        System.out.println("age 內存偏移地址:" + ageOffset);

        System.out.println("---------------------");

        // CAS操做
        int currentValue = unsafe.getIntVolatile(user, ageOffset);
        System.out.println("age內存當前值:" + currentValue);
        boolean casAge = unsafe.compareAndSwapInt(user, ageOffset, 17, 18);
        System.out.println("age進行CAS更新成功:" + casAge);
        System.out.println("age更新後的值:" + user.getAge());

        System.out.println("---------------------");

        // volatile修飾,保證可見性、有序性
        unsafe.putObjectVolatile(user, nameOffset, "test");
        System.out.println("name更新後的值:" + unsafe.getObjectVolatile(user, nameOffset));

    }
}

結果輸出atom

name內存偏移地址:16
age 內存偏移地址:12
---------------------
age內存當前值:17
age進行CAS更新成功:true
age更新後的值:18
---------------------
name更新後的值:test

Unsafe 中 CAS 操做是原子性的,因此在秒殺、庫存扣減中也能夠使用 Unsafe 來扣減庫存。spa

結語

本文對 Java 中的 sun.misc.Unsafe 的用法及應用場景進行了基本介紹,僅作後續源碼閱讀的鋪墊。到此,本篇文章就寫完了,感謝你們的閱讀!若是您以爲對您有幫助,請關注公衆號【當我趕上你】。命令行

相關文章
相關標籤/搜索