本篇文章是系列文章《跟我一块儿剖析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…