【Java】Unsafe源碼走讀及實戰應用

前言

  • Unsafe顧名思義,它不安全,要小心使用
  • Unsafe可以控制對象的內存申請和釋放,可以對內存訪問進行控制
  • Unsafe本身僅是爲JDK服務的,不推薦應用程序直接使用,且JDK可能隨時會改動它

以下演示的JDK版本:1.8

1 使用

部分源碼:

Unsafe:
//私有變量
private static final Unsafe theUnsafe;

//私有構造函數
private Unsafe(){}

//私有變量初始化
static {
	......
	theUnsafe = new Unsafe();
	......
}

//公開獲取Unsafe對象
public static Unsafe getUnsafe() {
	Class arg = Reflection.getCallerClass();
	//判斷當前ClassLoader是否是頂層類加載器(null),所以一般情況下,只有rt.jar中的類可以使用Unsafe對象
	if (!VM.isSystemDomainLoader(arg.getClassLoader())) {
	    throw new SecurityException("Unsafe");
	} else {
	    return theUnsafe;
	}
}
VM:
//判斷ClassLoader是否是頂層類加載器(null)
public static boolean isSystemDomainLoader(ClassLoader arg) {
	return arg == null;
}

分析:

  • Unsafe類是final類型,無法被其他類繼承
  • Unsafe構造函數爲私有,不能通過常規途徑創建對象
  • Unsafe成員變量theUnsafe爲私有,無法被外部訪問
  • 唯一的公開方法getUnsafe,限制了只有rt.jar才能使用(或者是-Xbootclasspath指定class)

那咋辦?只能靠反射了,通過構造函數或成員變量,都能獲取Unsafe對象:

/**
* 通過私有構造函數Unsafe()獲取Unsafe
* 
* @return
* @throws Exception
*/
public static Unsafe getUnsafeByContructor() throws Exception {
	// 獲取私有無參構造函數Unsafe()
	Constructor<Unsafe> constructor = Unsafe.class.getDeclaredConstructor();
	// 關閉安全檢查,不代表一定可以訪問,但一定可以減小安全檢查的資源消耗,常用於反射類功能中
	constructor.setAccessible(true);
	Unsafe unsafe = (Unsafe) constructor.newInstance();
	return unsafe;
}
/**
* 通過私有成員變量theUnsafe獲取Unsafe對象
* 
* @return
* @throws Exception
*/
public static Unsafe getUnsafeByField() throws Exception {
	// 獲取私有成員變量theUnsafe
	Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
	// 關閉安全檢查,不代表一定可以訪問,但一定可以減小安全檢查的資源消耗,常用於反射類功能中
	unsafeField.setAccessible(true);
	Unsafe unsafe = (Unsafe) unsafeField.get(null);
	return unsafe;
}

2 併發安全

2.1 CAS

一種可以支持併發修改的判斷:

//1:待更新對象;2:內存地址-偏移量;3:當前預期值;4:待更新值;
//更新成功返回true,否則爲false
public final native boolean compareAndSwapObject(Object arg0, long arg1, Object arg3, Object arg4);
public final native boolean compareAndSwapInt(Object arg0, long arg1, int arg3, int arg4);
public final native boolean compareAndSwapLong(Object arg0, long arg1, long arg3, long arg5);

2.2 volatile

Object類型和8種基礎數據類型的18個Get和Put方法,包含volatile特性:工作內存中的值永遠最新;禁止指令重排。

public native Object getObjectVolatile(Object arg0, long arg1);
public native void putObjectVolatile(Object arg0, long arg1, Object arg3);
public native int getIntVolatile(Object arg0, long arg1);
public native void putIntVolatile(Object arg0, long arg1, int arg3);
public native boolean getBooleanVolatile(Object arg0, long arg1);
public native void putBooleanVolatile(Object arg0, long arg1, boolean arg3);
public native byte getByteVolatile(Object arg0, long arg1);
public native void putByteVolatile(Object arg0, long arg1, byte arg3);
public native short getShortVolatile(Object arg0, long arg1);
public native void putShortVolatile(Object arg0, long arg1, short arg3);
public native char getCharVolatile(Object arg0, long arg1);
public native void putCharVolatile(Object arg0, long arg1, char arg3);
public native long getLongVolatile(Object arg0, long arg1);
public native void putLongVolatile(Object arg0, long arg1, long arg3);
public native float getFloatVolatile(Object arg0, long arg1);
public native void putFloatVolatile(Object arg0, long arg1, float arg3);
public native double getDoubleVolatile(Object arg0, long arg1);
public native void putDoubleVolatile(Object arg0, long arg1, double arg3);

2.3 CAS+Volatile

//給對象增加指定值:1-對象;2-內存地址;3-增加值
public final int getAndAddInt(Object arg0, long arg1, int arg3) {
	int arg4;
	do {
	    arg4 = this.getIntVolatile(arg0, arg1);
	} while (!this.compareAndSwapInt(arg0, arg1, arg4, arg4 + arg3));

	return arg4;
}

public final long getAndAddLong(Object arg0, long arg1, long arg3) {
	......
}
//給對象賦予指定值:1-對象;2-內存地址;3-賦予值
public final int getAndSetInt(Object arg0, long arg1, int arg3) {
	int arg4;
	do {
	    arg4 = this.getIntVolatile(arg0, arg1);
	} while (!this.compareAndSwapInt(arg0, arg1, arg4, arg3));

	return arg4;
}

public final long getAndSetLong(Object arg0, long arg1, long arg3) {
	......
}

public final Object getAndSetObject(Object arg0, long arg1, Object arg3) {
	......
}

2.4 內存屏障

//內存屏障,禁止load操作重排序,即屏障前的load操作不能被重排序到屏障後,屏障後的load操作不能被重排序到屏障前
//即a=1;loadFence();b=2;那麼工作內存中一定是先發生a=1,然後再發生b=2;
public native void loadFence();

//內存屏障,禁止store操作重排序,即屏障前的store操作不能被重排序到屏障後,屏障後的store操作不能被重排序到屏障前
//即a=1;storeFence();b=2;那麼主內存中一定是先發生a=1,然後再發生b=2;工作內存中,可能先b=2,後a=1;
public native void storeFence();

//內存屏障,禁止load、store操作重排序
//即a=1;fullFence();b=2;那麼工作內存和主內存都是先發生a=1,然後再發生b=2;
public native void fullFence();

volatile和內存屏障不瞭解的可以看:java內存模型與線程

2.5 有序寫入

//設置對象var1中offset偏移地址var2對應的Object型field的值爲指定值var4
public native void putOrderedObject(Object var1, long var2, Object var4);

public native void putOrderedInt(Object var1, long var2, int var4);

public native void putOrderedLong(Object var1, long var2, long var4);

3 其他功能

僅瞭解即可,用到或見到時候便於查看。

3.1 非堆內存操作

//獲得本地指針
public native long getAddress(long address);
//存儲本地指針到給定的內存地址
public native void putAddress(long address, long x);
  
//分配內存
public native long allocateMemory(long bytes);
//重新分配內存
public native long reallocateMemory(long address, long bytes);

//初始化內存內容
public native void setMemory(Object o, long offset, long bytes, byte value);
public void setMemory(long address, long bytes, byte value) {
 setMemory(null, address, bytes, value);
}

//內存內容拷貝
public native void copyMemory(Object srcBase, long srcOffset,Object destBase, long destOffset,long bytes);
public void copyMemory(long srcAddress, long destAddress, long bytes) {
 copyMemory(null, srcAddress, null, destAddress, bytes);
}

//釋放內存
public native void freeMemory(long address)

//返回指針的大小,返回值爲4或8
public native int addressSize();
//內存頁的大小
public native int pageSize();  

3.2 偏移量操作

//靜態屬性存儲分配中的位置(偏移地址)。
public native long staticFieldOffset(Field arg0);

//非靜態屬性存儲分配中的位置(偏移地址)
public native long objectFieldOffset(Field arg0);

//返回給定的靜態屬性的位置,配合staticFieldOffset方法使用。返回值是靜態屬性所在的Class對象的一個內存快照
//例:unsafe.getObject(unsafe.staticFieldBase(name), unsafe.staticFieldOffset(name))
public native Object staticFieldBase(Field arg0);

//返回數組中第一個元素實際地址相對整個數組對象的地址的偏移量
public native int arrayBaseOffset(Class<?> var1);

//返回數組中第一個元素所佔用的內存空間
public native int arrayIndexScale(Class<?> var1);

3.3 對象操作

類似2.2volatile數據操作,僅是沒有volatile特性,同樣有18個方法,僅展示int類型的:

//獲得給定地址上的int值
public native int getInt(long address);
//設置給定地址上的int值
public native void putInt(long address, int x);

3.4 線程控制

//取消阻塞線程
public native void unpark(Object thread);
//阻塞線程
public native void park(boolean isAbsolute, long time);

3.5 Class操作

//檢測給定的類是否需要初始化。使用在獲取一個類的靜態屬性的時候(因爲一個類如果沒初始化,它的靜態屬性也不會初始化)
public native boolean shouldBeInitialized(Class<?> arg0);

//檢測給定的類是否已經初始化,使用場景同上
public native void ensureClassInitialized(Class<?> arg0);

//定義一個類,返回類實例,此方法會跳過JVM的所有安全檢查。
public native Class<?> defineClass(String name, byte[] b, int off, int len,ClassLoader loader,ProtectionDomain protectionDomain);

//定義一個匿名類,可用於動態創建類
public native Class<?> defineAnonymousClass(Class<?> hostClass, byte[] data, Object[] cpPatches);

//創建一個類的實例,但不會調用這個實例的構造方法,如果這個類還未被初始化,則初始化這個類
public native Object allocateInstance(Class<?> var1) throws InstantiationException;

4 實現自定義原子類

直接看代碼:

public class CASTest {

    public static CountDownLatch latch;

    public static void main(String[] args) throws Exception {
        CASInteger CASInteger = new CASInteger(0);
        Runnable casAdd = () -> {
            //每個線程修改1萬次
            for(int i = 0; i < 10000; i++){
                CASInteger.casIncreament();
            }
            latch.countDown();
        };
        //線程數量100個
        int threadNum = 100;
        latch = new CountDownLatch(threadNum);
        long start = System.currentTimeMillis();
        //啓動所有線程
        for(int i = 0; i < threadNum; i++){
            new Thread(casAdd).start();
        }
        //等待所有線程結束,
        latch.await();
        System.out.println("time cost : " + (System.currentTimeMillis() - start)+" ms");
        System.out.println("result: " + CASInteger.getValue());
    }
}

/**
 * 自定義CAS-Integer,Thread Safe
 */
class CASInteger {
    private sun.misc.Unsafe unsafe;
    private long offset;
    private volatile int value ;

    public CASInteger(int value) throws Exception {
        this.value = value;
        //獲取Unsafe對象
        unsafe = getUnsafeByContructor();
        //當前對象的偏移量
        offset = unsafe.objectFieldOffset(CASInteger.class.getDeclaredField("value"));
    }

    /**
     * 安全自增,僅當更新值比當前對象值多1時,才能更新成功。
     */
    public void casIncreament(){
        //效果等價於:unsafe.getAndAddInt(this, offset, 1);
        while (!unsafe.compareAndSwapInt(this, offset, value, value + 1)){
        }
    }

    public int getValue() {
        return value;
    }

    /**
     * 通過私有構造函數Unsafe()獲取Unsafe
     *
     * @return
     * @throws Exception
     */
    private static Unsafe getUnsafeByContructor() throws Exception {
        // 獲取私有無參構造函數Unsafe()
        Constructor<Unsafe> constructor = Unsafe.class.getDeclaredConstructor();
        // 關閉安全檢查,不代表一定可以訪問,但一定可以減小安全檢查的資源消耗,常用於反射類功能中
        constructor.setAccessible(true);
        Unsafe unsafe = (Unsafe) constructor.newInstance();
        return unsafe;
    }
}

結果:146毫秒,自增100萬次,數據正確

time cost : 113 ms
result: 1000000

PS:value類型是volatile的int類型,爲了保證數據可見;compareAndSwapInt傳參必須是int,不能是Integer,不然可能會內存奔潰哦。

 

 


愛家人,愛生活,愛設計,愛編程,擁抱精彩人生!

發佈了94 篇原創文章 · 獲贊 232 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章