【詳解】Java高併發之UnSafe類

簡介

Unsafe類是在sun.misc包下,不屬於Java標準。但是很多Java的基礎類庫,包括一些被廣泛使用的高性能開發庫都是基於Unsafe類開發的,比如Netty、Cassandra、Hadoop、Kafka等。Unsafe類在提升Java運行效率,增強Java語言底層操作能力方面起了很大的作用。

Java和C++語言的一個重要區別就是Java中我們無法直接操作一塊內存區域,不能像C++中那樣可以自己申請內存和釋放內存。Java中的Unsafe類爲我們提供了類似C++手動管理內存的能力,同時也有了指針的問題。

首先,Unsafe類是"final"的,不允許繼承。且構造函數是private的:

public final class Unsafe {
    private static final Unsafe theUnsafe;
    public static final int INVALID_FIELD_OFFSET = -1;

    private static native void registerNatives();
    // 構造函數是private的,不允許外部實例化
    private Unsafe() {
    }
    ...
}

因此我們無法在外部對Unsafe進行實例化。

獲取Unsafe

Unsafe無法實例化,那麼怎麼獲取Unsafe呢?答案就是通過反射來獲取Unsafe:

public Unsafe getUnsafe() throws IllegalAccessException {
    Field unsafeField = Unsafe.class.getDeclaredFields()[0];
    unsafeField.setAccessible(true);
    Unsafe unsafe = (Unsafe) unsafeField.get(null);
    return unsafe;
}

主要功能

Unsafe的功能如下圖:
在這裏插入圖片描述

CAS相關

JUC中大量運用了CAS操作,可以說CAS操作是JUC的基礎,因此CAS操作是非常重要的。Unsafe中提供了int,long和Object的CAS操作:

public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

偏移量相關

public native long staticFieldOffset(Field var1);

public native long objectFieldOffset(Field var1);

  • staticFieldOffset方法用於獲取靜態屬性Field在對象中的偏移量,讀寫靜態屬性時必須獲取其偏移量。
  • objectFieldOffset方法用於獲取非靜態屬性Field在對象實例中的偏移量,讀寫對象的非靜態屬性時會用到這個偏移量

類加載

public native Class<?> defineClass(String var1, byte[] var2, int var3, int var4, ClassLoader var5, ProtectionDomain var6);

public native Class<?> defineAnonymousClass(Class<?> var1, byte[] var2, Object[] var3);

public native Object allocateInstance(Class<?> var1) throws InstantiationException;

public native boolean shouldBeInitialized(Class<?> var1);

public native void ensureClassInitialized(Class<?> var1);
  • defineClass方法定義一個類,用於動態地創建類。
  • defineAnonymousClass用於動態的創建一個匿名內部類。
  • allocateInstance方法用於創建一個類的實例,但是不會調用這個實例的構造方法,如果這個類還未被初始化,則初始化這個類。
  • shouldBeInitialized方法用於判斷是否需要初始化一個類。
  • ensureClassInitialized方法用於保證已經初始化過一個類。

舉例

public class UnsafeFooTest {
    private static Unsafe geUnsafe() {

        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            return (Unsafe) f.get(null);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;

    }


    static class Simple {
        private long l = 0;

        public Simple() {
            this.l = 1;
            System.out.println("我被初始化了");
        }

        public long getL() {
            return l;
        }
    }


    public static void main(String[] args) throws Exception {
//        Simple simple1 = new Simple();
//        System.out.println(simple1.getL());
//        Simple simple2 = Simple.class.newInstance();

        Unsafe unsafe = geUnsafe();
        //可以繞過類的初始化,不使用
        Simple s = (Simple) unsafe.allocateInstance(Simple.class);
        System.out.println(s.getL());
    }
}

結果:

0

  • 可以發現,利用Unsafe獲取實例,不會調用構造方法

普通讀寫

通過Unsafe可以讀寫一個類的屬性,即使這個屬性是私有的,也可以對這個屬性進行讀寫。

讀寫一個Object屬性的相關方法

public native int getInt(Object var1, long var2);

public native void putInt(Object var1, long var2, int var4);
  • getInt用於從對象的指定偏移地址處讀取一個int。
  • putInt用於在對象指定偏移地址處寫入一個int。其他的primitive type也有對應的方法。

舉例


public class UnsafeFooTest {
    private static Unsafe geUnsafe() {

        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            return (Unsafe) f.get(null);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;

    }


    static class Guard{
        private int ACCESS_ALLOWED = 1;
        private boolean allow(){
            return 50 == ACCESS_ALLOWED;
        }

        public void work(){
            if (allow()){
                System.out.println("我被允許工作....");
            }
        }
    }


    public static void main(String[] args) throws Exception {
        Unsafe unsafe = geUnsafe();
        Guard guard = new Guard();
//        guard.work();

        Field f = guard.getClass().getDeclaredField("ACCESS_ALLOWED");
        //直接給那塊內存賦值
        unsafe.putInt(guard,unsafe.objectFieldOffset(f),50);
        System.out.println("強行賦值...");
        guard.work();

    }
}

結果

強行賦值…
我被允許工作…

類加載

public native Class<?> defineClass(String var1, byte[] var2, int var3, int var4, ClassLoader var5, ProtectionDomain var6);

public native Class<?> defineAnonymousClass(Class<?> var1, byte[] var2, Object[] var3);

public native Object allocateInstance(Class<?> var1) throws InstantiationException;

public native boolean shouldBeInitialized(Class<?> var1);

public native void ensureClassInitialized(Class<?> var1);
  • defineClass方法定義一個類,用於動態地創建類。
  • defineAnonymousClass用於動態的創建一個匿名內部類。
  • allocateInstance方法用於創建一個類的實例,但是不會調用這個實例的構造方法,如果這個類還未被初始化,則初始化這個類。
  • shouldBeInitialized方法用於判斷是否需要初始化一個類。
  • ensureClassInitialized方法用於保證已經初始化過一個類。

內存屏障

public native void loadFence();

public native void storeFence();

public native void fullFence();
  • loadFence:保證在這個屏障之前的所有讀操作都已經完成。
  • storeFence:保證在這個屏障之前的所有寫操作都已經完成。
  • fullFence:保證在這個屏障之前的所有讀寫操作都已經完成。

線程調度

public native void unpark(Object var1);

public native void park(boolean var1, long var2);

public native void monitorEnter(Object var1);

public native void monitorExit(Object var1);

public native boolean tryMonitorEnter(Object var1);
  • park方法和unpark方法相信看過LockSupport類的都不會陌生,這兩個方法主要用來掛起和喚醒線程。
  • LockSupport中的park和unpark方法正是通過Unsafe來實現的:
// 掛起線程
public static void park(Object blocker) {
    Thread t = Thread.currentThread();
    setBlocker(t, blocker); // 通過Unsafe的putObject方法設置阻塞阻塞當前線程的blocker
    UNSAFE.park(false, 0L); // 通過Unsafe的park方法來阻塞當前線程,注意此方法將當前線程阻塞後,當前線程就不會繼續往下走了,直到其他線程unpark此線程
    setBlocker(t, null); // 清除blocker
}

// 喚醒線程
public static void unpark(Thread thread) {
    if (thread != null)
        UNSAFE.unpark(thread);
}

monitorEnter方法和monitorExit方法用於加鎖,Java中的synchronized鎖就是通過這兩個指令來實現的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章