本篇文章是系列文章《跟我一塊兒剖析Java併發源碼》的第一篇文章。之後會每週保持定時更新。這些系列文章算是對《java併發編程系統與模型》的一些補充吧,真心但願你們能支持這本書,您的支持就是我最大的動力!
在java併發相關的源代碼學習中,有一個類常常出現,這個類就是位於sun.misc包中的Unsafe類。好比,屬於Java併發包中最重要的類之一的AbstractQueuedSynchronizer中就常常調用這個類的方法。
今天就來簡單剖析一下這個類。
Unsafe類是一個很低級別的類,執行低級別的不安全的操做。因此使用的時候要當心,只有那些得到信任的代碼才能調用。爲何說它是比較低級的呢?由於它能直接操做任意的內存。那爲何它是危險的呢?由於它能直接操做任意的內存。javascript
Unsafe類方法衆多,一一講述沒太必要,聰明的大家看完這篇文章再看看源碼理解其餘方法絕對沒什麼問題。先來看看有compareAndSwap開頭的一系列方法,從名字就能夠看出這確定是使用的CAS算法。CAS算法對這裏再也不詳細說明了。在《java併發編程系統與模型》這本書有詳細敘述。java
隨便拿一個compareAndSwapInt舉例:算法
public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);複製代碼
這個方法能夠在在對象o的內存偏移offset後與指望值比較,若是等於指望值,就更新爲x。因爲是直接操做內存的。好比要這樣更新一個對象的某個屬性,就要獲得這個屬性在內存中的偏移量。unsafe提供了objectFieldOffset方法來獲得某個屬性在對象中的偏移量:編程
public native long objectFieldOffset(Field f);複製代碼
好比有這個一個對象:bootstrap
class User{
private String name
}複製代碼
要獲得屬性name 偏移量, 就可使用安全
nameOffset = unsafe.objectFieldOffset(User.class.getDeclaredField("name"));複製代碼
還有一些put方法,好比併發
public native void putOrderedInt(Object obj, long offset, int value);複製代碼
就是直接把這個對象內存偏移offset而後直接賦int值。性能
Unsafe類是一個受保護的類,是不能直接在程序中使用的。直接的使用會拋出SecurityException異常,下面來測驗一下(import sun.misc.Unsafe 須要手工添加,Eclispe或者其餘IDE並不會直接提示):學習
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import sun.misc.Unsafe;
public class UnSafeTest {
public static void main(String[] args) {
try {
Unsafe unsafe = Unsafe.getUnsafe();
User user = new User();
long ageOffset = unsafe.objectFieldOffset(filed);
unsafe.putInt(user, ageOffset, 10);
System.out.println(user.getAge());
System.out.println(unsafe);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}複製代碼
查看獲取實例的方法中有一個VM.isSystemDomainLoader檢測,若是不是的話,會拋出SecurityException:ui
@CallerSensitive
public static Unsafe getUnsafe() {
Class<?> caller = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(caller.getClassLoader()))
throw new SecurityException("Unsafe");
return theUnsafe;
}複製代碼
來看看M.isSystemDomainLoader內部的方法:
public static boolean isSystemDomainLoader(ClassLoader loader) {
return loader == null;
}複製代碼
在Java中,若是一個對象的classLoader等於null,這就說明這個對象的類加載器是boostrap classloader,那麼若是類是由bootstrap classloader加載的話,那麼它就是受信任的代碼。
能夠直接打印String的ClassLoader來檢測一下結論是否正確:
System.out.println(String.class.getClassLoader());
理論上有兩種方法能夠打破這種限制。一種就是將User類變成SystemDomainLoader,JAVA自己的類加載機制致使了改變SystemDomainLoader方法暫時較難作到,通用的都是經過反射的方法。一種是經過反射其實體變量theUnsafe:
public class UnSafeTest {
public static void main(String[] args) {
try {
User user = new User();
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe UNSAFE = (Unsafe) theUnsafe.get(null);
System.out.println(UNSAFE);
Field filed = user.getClass().getDeclaredField("age");
long ageOffset = UNSAFE.objectFieldOffset(filed);
UNSAFE.putInt(user, ageOffset, 10);
System.out.println(user.getAge());
} catch (Exception e) {
e.printStackTrace();
}
}
}複製代碼
打印user對象的age屬性,結果輸出了10。
另一種就是經過其構造器反射,從新獲得一個實例:
public class UnSafeTest {
public static void main(String[] args) {
try {
Constructor<Unsafe> con = Unsafe.class.getDeclaredConstructor();
// 用該私有構造方法建立對象
// IllegalAccessException:非法的訪問異常。
// 暴力訪問
con.setAccessible(true);// 值爲true則指示反射的對象在使用時應該取消Java語言訪問檢查。
User user = new User();
System.out.println(UNSAFE);
// Unsafe unsafe =(Unsafe) clazz.newInstance();
Field filed = user.getClass().getDeclaredField("age");
long ageOffset = UNSAFE.objectFieldOffset(filed);
UNSAFE.putInt(user, ageOffset, 10);
System.out.println(user.getAge());
} catch (Exception e) {
e.printStackTrace();
}
}
}複製代碼
其中
Constructor<Unsafe> con = Unsafe.class.getDeclaredConstructor();複製代碼
也能夠替換成class.forname的形式:
Constructor<Unsafe> con = (Constructor<Unsafe>) Class.forName("sun.misc.Unsafe").getDeclaredConstructor();複製代碼
若是說被修改的屬性是一個基本類型,那麼直接操做內存的優點並不大。可是若是被修改的屬性是一個對象,差異就比較大了。不信來作一個很是簡單的比較:
public class User {
private Integer age;
public int getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}複製代碼
public class UnSafeTest {
public static void main(String[] args) {
try {
Constructor<Unsafe> con = (Constructor<Unsafe>) Class.forName("sun.misc.Unsafe").getDeclaredConstructor();
con.setAccessible(true);
User user = new User();
Unsafe UNSAFE = con.newInstance(null);
Field filed = user.getClass().getDeclaredField("age");
long s1=System.currentTimeMillis();
for(int i=0;i<1000000;i++){
user.setAge(i);
}
System.out.println(System.currentTimeMillis()-s1);
long ageOffset = UNSAFE.objectFieldOffset(filed);
long s2=System.currentTimeMillis();
for(int i=0;i<1000000;i++){
UNSAFE.putInt(user, ageOffset, i);
}
System.out.println(System.currentTimeMillis()-s2);
} catch (Exception e) {
e.printStackTrace();
}
}
}複製代碼
set方法和putInt各執行一百萬次,性能差了好幾倍。若是被修改的屬性是一個很是複雜的對象的話,性能差距會更大。由於每次set值的時候,JVM內部依舊會每次去找這個對象屬性的內存偏移量。如今我直接將偏移量拿出來了,不用每次找偏移量了,速度加快那是必然滴,固然被修改的對象確定是一個對象。
在下一週分析AbstractQueuedSynchronizer類的時候,還會結合AbstractQueuedSynchronizer類實現中如何具體的使用Unsafe類進行說明,這裏就暫時告一段落了。
小參考:
bandrzejczak.com/blog/2015/0…
stackoverflow.com/questions/1…
stackoverflow.com/questions/2…