前两天在看 Android 平台上的 FutureTask 类的时候无意看到了 Unsafe,当时这个类是报红色的属于 sun.* API 肿的类,并且不是 J2SE 中真正的一部分,因为你很有可能找不到任何的官方信息,但是你可以在 Android 源代码中可以找到该代码的实现以及 Native 的实现,这个有利于我们的学习和使用。该类在 Android4.4 系统的 Art 虚拟机和 Davilk 虚拟中的代码中,其路径分别为:
1) libcore/libart/src/main/java/sun/misc/Unsafe.java 2) libcore/libdvm/src/main/java/sun/misc/Unsafe.java
我们平时都知道 Java 层的代码并不可以直接访问系统底层,这既保障了虚拟机的安全,又屏蔽了底层方便的差异化,可以很好的让程序在上层代码中去实现功能和业务逻辑。不过有时候我们需要直接像 C 那样子可以直接访问内存里面的内容并且修改其值,这个时候 Unsafe 类就在关键的时候可以更好的去实现了。Unsafe 类提供了硬件级别的原子操作,主要提供了以下的功能:
由于 Unsafe 类是一个比较特殊的,所以并没有出现在 JDK 文档中,其介绍也非常的好所以一般调用的时候也不会随随便便的让别人调用的,这里我们就需要通过的反射的方式来调用其对象,我们可以通过反射 getUnsafe() 方法来获取对象,也可以通过获取 theUnsafe 字段并且设置其访问的属性。从代码中我们看到了通过 getSafe() 方式直接获取 Unsafe 对象的时候都做了类加载器的判断。所以我们通过反射的方式来获取该对象是比较好的。
- /** Traditional dalvik name. */
- private static finalUnsafe THE_ONE =newUnsafe();/** Traditional RI name. */
- //也可以通过反射该字段并且修改其访问属性为 true。
- private static finalUnsafe theUnsafe = THE_ONE;private Unsafe() {}// 这里我们可以通过反射的方法获取Unsafe的对象
- public staticUnsafegetUnsafe() {/*
- * Only code on the bootclasspath is allowed to get at the
- * Unsafe instance.
- */ClassLoader calling = VMStack.getCallingClassLoader();if((calling !=null) && (calling != Unsafe.class.getClassLoader())) {throw newSecurityException("Unsafe access denied");
- }returnTHE_ONE;
- }
创建好了 Unsafe 对象之后,我们就可以使用 allocateInstance 来给某个类创建一个对象,我们也可以说是分配内存空间
- /**
- * Allocates an instance of the given class without running the constructor.
- * The class' <clinit> will be run, if necessary.
- */
- public nativeObjectallocateInstance(Class c);
在创建完对象之后,首先我们就可以通过
获取到该对象中的某个字段的内存地址。
- objectFieldOffset()
- /**
- * Gets the raw byte offset from the start of an object's memory to
- * the memory used to store the indicated instance field.
- *
- * @paramfield non-null; the field in question, which must be an
- * instance field
- * @returnthe offset to the field
- */
- public long objectFieldOffset(Field field) {if(Modifier.isStatic(field.getModifiers())) {throw newIllegalArgumentException("valid for instance fields only");
- }returnfield.getOffset();
- }
在 C 或者是 C++ 代码中我们可以通过指针来获取某个对象的内存地址来操纵该对象的,在 Unsafe 中我们使用
来设置值,使用
- putInt()
来获取对象的字段值。
- getInt()
更新字段内容方法:
- /**
- * 通过字段的偏移地址给某个对象字段设置对象值,比如说给String类型的字段设置内容。
- * 因为String是一个对象并且基本数值
- */
- public native void putObject(Object obj,longoffset, Object newValue);/**
- * 通过字段的偏移地址给某个对象字段设置对象值,比如说给String类型的字段设置内容。
- * 因为String是一个对象并且基本数值
- */
- public native void putOrderedObject(Object obj,longoffset, Object newValue);public native void putLong(Object obj,longoffset,longnewValue);public native void putLongVolatile(Object obj,longoffset,longnewValue);public native void putIntVolatile(Object obj,longoffset,intnewValue);public native void putOrderedLong(Object obj,longoffset,longnewValue);public native void putInt(Object obj,longoffset,intnewValue);/**
- * 通过字段的便宜地址给int对象字段设置值
- * @paramobj 需要设置内容对象值
- * @paramoffset 字段的偏移地址
- * @paramnewValue 需要更新的新值
- */
- public native void putOrderedInt(Object obj,longoffset,intnewValue);
上述的 putInt() 和 putIntVolatile() 主要区别是是在对象的字段上是否使用了 volatile 个人仅仅只是猜测而已,但是在平时测试的 demo 中并没有发现什么异常问题而 putOrderedInt 发现使用起来感觉跟前两个没有什么差别的。这几个方法的用法与差别如果有高手知道的话,可以在评论里大家一起相互交流和学习。
获取字段内容方法
- /**
- * 根据偏移地址来获取obj对象中字段内容
- * @paramobj 获取内容的对象
- * @paramoffset 字段的偏移地址
- */
- public native long getLong(Object obj,longoffset);public native long getLongVolatile(Object obj,longoffset);public native int getInt(Object obj,longoffset);public native int getIntVolatile(Object obj,longoffset);public nativeObjectgetObject(Object obj,longoffset);public nativeObjectgetObjectVolatile(Object obj,longoffset);
通过上面的方法我们清晰的知道了如何创建一个对象并且操作对象的内容了;以前我们在学习 C++ 指针的时候知道数组其实我们可以通过获取数组的首地址,然后在获取某个元素的偏移地址,因为是数组所以其偏移地址都相等的,所以我们就可以递归等值数列来获取每个元素的地址。
- /**
- * Gets the offset from the start of an array object's memory to
- * the memory used to store its initial (zeroeth) element.
- * @paramclazz non-null; class in question; must be an array class
- * @returnthe offset to the initial element
- */
- //获取数组的第一个位置的内存地址
- public int arrayBaseOffset(Class clazz) {
- Class component = clazz.getComponentType();if(component ==null) {throw newIllegalArgumentException("Valid for array classes only: "+ clazz);
- }// TODO: make the following not specific to the object model.
- intoffset =12;if(component ==long.class || component ==double.class) {
- offset +=4;// 4 bytes of padding.}returnoffset;
- }/**
- * Gets the size of each element of the given array class.
- *
- * @paramclazz non-null; class in question; must be an array class
- * @return> 0; the size of each element of the array
- */
- //获取数组中每个元素占用的内存大小,我们也可以说是偏移地址。
- public int arrayIndexScale(Class clazz) {
- Class component = clazz.getComponentType();if(component ==null) {throw newIllegalArgumentException("Valid for array classes only: "+ clazz);
- }// TODO: make the following not specific to the object model.
- if(!component.isPrimitive()) {return 4;
- }else if(component ==long.class || component ==double.class) {return 8;
- }else if(component ==int.class || component ==float.class) {return 4;
- }else if(component ==char.class || component ==short.class) {return 2;
- }else{// component == byte.class || component == boolean.class.
- return 1;
- }
- }
对于线程的挂起与恢复这里我不做过多的介绍,后面我将会使用一篇专门的博客来介绍
类,该类其实的主要实现就是使用 Unsafe 类下面的两个类。
- LockSupport.java
- /**
- * Parksthecalling threadfor thespecified amountof time,
- * unlessthe "permit" for thethreadisalready available (dueto* a previous callto{@link#unpark}. This method may also return* spuriously (that is,without thethread being toldtounpark
- *and without theindicated amountof timeelapsing).
- *
- * @param absolute whetherthe given timevalueisabsolute
- * @paramtime the(absolute millisorrelative nanos)timevalue
- */
- public void park(booleanabsolute, longtime) {if(absolute) {
- Thread.currentThread().parkUntil(time);
- }else{
- Thread.currentThread().parkFor(time);
- }
- }
- /**
- * Unparksthe givenobject, which must be a {@link Thread}.
- * @param obj non-null;theobjecttounpark
- */
- public void unpark(Object obj) {if(obj instanceof Thread) {
- ((Thread) obj).unpark();
- }else{
- throw new IllegalArgumentException("valid for Threads only");
- }
- }
下面我们使用 Unsafe 类中的 compareAndSwapXXX 方法来实现并发的操作, 其主要的原理是 CAS 操作,CAS 操作也是其他语言并发操作的基本理论,现代 CPU 广泛的支持的一种对内存中的共享数据进行操作的一种特殊指令,这个指令会对内存中的共享数据做原子的读写操作。CAS 基本原理是:CPU 会将内存中将要修改的数据会跟我们期望的数据进行比较,如果发现期望的数据跟内存中的不想等说明内存中的数据已经修改过了,这个时候并不会去更新内存的数据;如果两个数据相等的话才会将新的数据去替换内存的旧的数据。CAS 有 3 个操作数,内存地址 V,oldValue 旧的预期值 (该值主要是用于跟内存中的值就行比对),newValue 需要更新的数据值。只有旧的期望数据跟内存中的数据相等时才将新的数据更新到内存中。这是乐观锁的思路,它一开始就相信其他的线程并没有修改过内存中的内容,直到比对之后才进行操作;但是 synchronized 是一种悲观所,它认为在修改之前就有其他的线程去修改内存值了,所以就先把其他的线程给挂起,仅仅只让自己一个去修改,效率比较低。对于 CAS 的更多的例子分析我将会在
中进行更多的分析和讲解的。
- AtomicInteger
- /**
- * 通过对象字段的偏移地址offset来获取该字段的内存数据,然后利用该数据跟 expectedValue
- * 进行比较,如果相等则更新newValue,并且返回true,否则不更新newValue,并且返回false* @param objec 需要比较的内存对象
- * @paramoffset对象字段的内存偏移地址
- * @param expectedValue 期望值,就是用该值跟内存中的值进行比较
- * @param newValue 需要更新的新数据,
- */
- public nativebooleancompareAndSwapInt(Object obj, longoffset,
- int expectedValue, int newValue);
- /**
- * Performs a compare-and-setoperationona
- long
- * field withinthe givenobject.
- *
- * @param obj non-null; object containingthefield
- * @paramoffset offset to thefield within
- obj
- * @param expectedValue expected valueof thefield
- * @param newValue new valuetostorein thefieldif the contentsare
- *asexpected
- * @return
- trueif thenew value wasinfact stored,and*
- falseif not*/
- public nativebooleancompareAndSwapLong(Object obj, longoffset,
- long expectedValue, long newValue);
- /**
- * Performs a compare-and-setoperationonan
- Object
- * field (that is,a referencefield) withinthe givenobject.
- *
- * @param obj non-null; object containingthefield
- * @paramoffset offset to thefield within
- obj
- * @param expectedValue expected valueof thefield
- * @param newValue new valuetostorein thefieldif the contentsare
- *asexpected
- * @return
- trueif thenew value wasinfact stored,and*
- falseif not*/
- public nativebooleancompareAndSwapObject(Object obj, longoffset,
- Object expectedValue, Object newValue);
说了这么多可能都比较抽象什么的,下面我们就直接来演示一下具体的代码实现。首先我们写好反射的工具类 ReflectUtils.java
- public class ReflectUtils{
- private staticReflectUtils sInstance;private ReflectUtils() {
- }public staticReflectUtilsgetInstance() {if(sInstance ==null) {
- sInstance =newReflectUtils();
- }returnsInstance;
- }publicMethodgetMethod(Class clazz, String methodName, Class ...values) {
- Method method =null;try{if(values !=null) {
- method = clazz.getDeclaredMethod(methodName, values);
- }else{
- method = clazz.getDeclaredMethod(methodName);
- }
- }catch(NoSuchMethodException e) {
- e.printStackTrace();
- }returnmethod;
- }publicObjectinvokeStaticMethod(Method method, Object ...values) {returninvokeMethod(method,null, values);
- }publicObjectinvokeMethod(Method method, Object classValue, Object ...values) {if(method !=null) {try{returnmethod.invoke(classValue, values);
- }catch(IllegalAccessException e) {
- e.printStackTrace();
- }catch(InvocationTargetException e) {
- e.printStackTrace();
- }
- }return null;
- }/**
- * 这里我们通过反射静态方法 getUnsafe()方法来获取Unsafe对象,其实在之前的代码
- * 的时候我们也可以直接反射静态的成员变量来直接获取Unsafe对象的,其实都是差不多的。
- */
- publicObjectgetUnsafe(Class clazz) {
- Method method = getMethod(clazz,"getUnsafe");returninvokeStaticMethod(method);
- }
- }
紧接着我们反射 Unsafe 类中的方法,因为 Unsafe 对于我们来说不可见的,所以必须使用方法来实现。
- public classUnsafeProxy {private staticReflectUtils sUtils;private staticClass sUnsafeClass;private staticObject sUnsafe;static{try{
- sUtils = ReflectUtils.getInstance();
- sUnsafeClass = Class.forName("sun.misc.Unsafe");
- sUnsafe = sUtils.getUnsafe(sUnsafeClass);
- }catch(ClassNotFoundException e) {
- e.printStackTrace();
- }
- }public staticObjectallocateInstance(Classparams) {
- Method method = sUtils.getMethod(sUnsafeClass,"allocateInstance", Class.class);returnsUtils.invokeMethod(method, sUnsafe,params);
- }public static long objectFieldOffset(Field field) {
- Method method = sUtils.getMethod(sUnsafeClass,"objectFieldOffset", Field.class);
- Object obj = sUtils.invokeMethod(method, sUnsafe, field);returnobj ==null ? 0: (Long) obj;
- }public static long putLong(Objectobject,longoffset,longnewValue) {
- Method method = sUtils.getMethod(sUnsafeClass,"putLong", Object.class,long.class,long.class);
- Object obj = sUtils.invokeMethod(method, sUnsafe,object, offset, newValue);returnobj ==null ? 0: (Long) obj;
- }public static long putInt(Objectobject,longoffset,intnewValue) {
- Method method = sUtils.getMethod(sUnsafeClass,"putInt", Object.class,long.class,int.class);
- Object obj = sUtils.invokeMethod(method, sUnsafe,object, offset, newValue);returnobj ==null ? 0: (Long) obj;
- }public static long putObject(Objectobject,longoffset, Object newValue) {
- Method method = sUtils.getMethod(sUnsafeClass,"putObject", Object.class,long.class, Object.class);
- Object obj = sUtils.invokeMethod(method, sUnsafe,object, offset, newValue);returnobj ==null ? 0: (Long) obj;
- }public static int arrayIndexScale(Class clazz) {
- Method method = sUtils.getMethod(sUnsafeClass,"arrayIndexScale", Class.class);
- Object obj = sUtils.invokeMethod(method, sUnsafe, clazz);returnobj ==null ? 0: (Integer) obj;
- }public static int arrayBaseOffset(Class clazz) {
- Method method = sUtils.getMethod(sUnsafeClass,"arrayBaseOffset", Class.class);
- Object obj = sUtils.invokeMethod(method, sUnsafe, clazz);returnobj ==null ? 0: (Integer) obj;
- }
有了上面的准备方法,接下来我们就可以直接使用这些工具方法了,下面我就直接在 Activity 中调用并且最后将结果打印出来。
- public class UnsafeActivity extends Activity{
- @Override
- protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);this.setContentView(R.layout.activity_unsage);//首先调用allocateInstance方法创建一个Student对象Student student2 = (Student) UnsafeProxy.allocateInstance(Student.class);try{
- Field ageField = student2.getClass().getDeclaredField("age");
- Field enterTimeField = student2.getClass().getDeclaredField("entertime");
- Field nameField = student2.getClass().getDeclaredField("name");//获取对象字段的偏移地址
- longageOffset = UnsafeProxy.objectFieldOffset(ageField);longnameOffset = UnsafeProxy.objectFieldOffset(nameField);longentertimeOffset = UnsafeProxy.objectFieldOffset(enterTimeField);//通过偏移地址我们来更新该字段的内存信息UnsafeProxy.putInt(student2, ageOffset,100);
- UnsafeProxy.putLong(student2, entertimeOffset,121202L);
- UnsafeProxy.putObject(student2, nameOffset,"王五");//通过数组的首地址以及每个元素的偏移地址来操作数组元素String stuNames[] =newString[5];longfirstAddr = UnsafeProxy.arrayBaseOffset(String[].class);longoffset = UnsafeProxy.arrayIndexScale(String[].class);//开始操作数组的第一个元素的内容,其首地址是firstAddr。UnsafeProxy.putObject(stuNames, firstAddr,"张三");
- UnsafeProxy.putObject(stuNames, firstAddr + offset,"李四");
- UnsafeProxy.putObject(stuNames, firstAddr +2* offset,"王五");
- UnsafeProxy.putObject(stuNames, firstAddr +3* offset,"赵六");
- UnsafeProxy.putObject(stuNames, firstAddr +4* offset,"孙七");for(intindex =0; index < stuNames.length; index++) {
- Log.i("LOH","method..."+ stuNames[index]);
- }
- Log.i("LOH","entertime..."+ student2.toString());
- }catch(NoSuchFieldException e) {
- e.printStackTrace();
- }
- }
- class Student {privateString name;private intage;private longentertime;@Override
- publicStringtoString() {return "Student{"+"name='"+ name +'\''+", age="+ age +", entertime="+ entertime +'}';
- }
- }
- }
PS: 通过上面的代码的代码只是例举了一部分 Unsafe 的功能,其中操作 int[] 数组的元素我还没有成功,具体不知道什么原因导致,还有 CAS 和 线程挂起与恢复之类的操作我这里暂时就没有举例,后面我会通过对 Atomic 系列进行结合的分析。该类可能在我们平时的时候用地方不多,但是对于我们掌握 Java 高并发里面核心的 API 有着非常重大作用,因为 Java 高并发的 API 可以说都是基于该类实现的,只不过很多的 API 做了更多的处理和包装而已,其最核心的东西还是 Unsafe 中的 compareAndSwapXXX 系列的方法。其中 Android 上有一个热修复的框架 JAndFix 就是使用的 Unsafe 来实现的。
JAndFix 地址: https://github.com/qiuba/JAndFix
来源: http://blog.csdn.net/u012417984/article/details/73332762