在研究JDK中AQS时,会发现这个类很多地方都使用了CAS操作,在并发实现中CAS操作必须具备原子性,而且是硬件级别的原子性,java被隔离在硬件之上,明显力不从心,这时为了能直接操作操作系统层面,肯定要通过用C++编写的native本地方法来扩展实现。JDK提供了一个类来满足CAS的要求,sun.misc.Unsafe,从名字上可以大概知道它用于执行低级别、不安全的操作,AQS就是使用此类完成硬件级别的原子操作。
Unsafe是一个很强大的类,它可以分配内存、释放内存、可以定位对象某字段的位置、可以修改对象的字段值、可以使线程挂起、使线程恢复、可进行硬件级别原子的CAS操作等等。
但平时我们没有这么特殊的需求去使用它,而且必须在受信任代码(一般由JVM指定)中调用此类,例如直接 Unsafe unsafe = Unsafe.getUnsafe();
获取一个Unsafe实例是不会成功的,因为这个类的安全性很重要,设计者对其进行了如下判断,它会检测调用它的类是否由启动类加载器Bootstrap ClassLoader(它的类加载器为null)加载,由此保证此类只能由JVM指定的类使用。判断逻辑如下,
public static Unsafe getUnsafe() { Class cc = sun.reflect.Reflection.getCallerClass(2); if (cc.getClassLoader() != null) throw new SecurityException("Unsafe"); return theUnsafe; } 复制代码
当然可以通过反射绕过上面的限制,用下面的getUnsafeInstance方法可以获取Unsafe实例,这段代码演示了如何获取java对象的相对地址偏移量及使用Unsafe完成CAS操作,最终输出的是flag字段的内存偏移量及CAS操作后的值。分别为8和101。另外如果使用开发工具如Eclipse,可能会编译通不过,只要把编译错误提示关掉即可。
public class UnsafeTest { privateint flag = 100; privatestatic long offset; privatestatic Unsafe unsafe = null; static{ try{ unsafe= getUnsafeInstance(); offset= unsafe.objectFieldOffset(UnsafeTest.class.getDeclaredField("flag")); }catch (Exception e) { e.printStackTrace(); } } publicstatic void main(String[] args) throws Exception { intexpect = 100; intupdate = 101; UnsafeTestunsafeTest = new UnsafeTest(); System.out.println("unsafeTest对象的flag字段的地址偏移量为:"+offset); unsafeTest.doSwap(offset,expect, update); System.out.println("CAS操作后的flag值为:" +unsafeTest.getFlag()); } privateboolean doSwap(long offset, int expect, int update) { returnunsafe.compareAndSwapInt(this, offset, expect, update); } publicint getFlag() { returnflag; } privatestatic Unsafe getUnsafeInstance() throws SecurityException,NoSuchFieldException,IllegalArgumentException,IllegalAccessException{ FieldtheUnsafeInstance = Unsafe.class.getDeclaredField("theUnsafe"); theUnsafeInstance.setAccessible(true); return(Unsafe) theUnsafeInstance.get(Unsafe.class); } } 复制代码
Unsafe类让我们明白了java是如何实现对操作系统操作的,一般我们使用java是不需要在内存中处理java对象及内存地址位置的,但有的时候我们确实需要知道java对象相关的地址,于是我们使用Unsafe类,尽管java对其提供了足够的安全管理。