一开始对Unsafe的认知仅仅局限于CAS操作,随着认知增加 ,实际上:
1. Unsafe Unsafe是 sun提供的工具sun.misc.Unsafe 。通过它我们可以使用更底层的手段来操作Java内存模型,例如:
保证原子性的交换操作: compareAndSwapObject() ,通常用它来原子的修改状态位,制造一个临界区。
常见的while(true) 重试使用它。
通过加屏障,具有可见的set操作 : putObjectVolatile ,通常用它来保证set操作的可见性,和禁止重排。
只保证 禁重排,但不立即保证可见性的set操作 : putOrderedObject 性能更好,在特殊场景下能够优化 putVolatile 。例如 Future中设置状态位(单向的,且状态位含义独立)。
分配内存 allocateMemory() , 回收内存 reallocateMemory() : nio用它来判断 平台的大小端。
获得数组 baseOffset arrayBaseOffset()
获得 数组元素的scale arrayIndexScale()
访问数组元素公式: 索引为n的元素在数组中的偏移量 indexOffset = base + n* scale
在j.u.c.a.AtomicIntegerArray 及类似原子数组中使用。
下面是相关代码的举例分析
2. 代码举例 2.1 如何拿到Unsafe 我们无法直接调用 Unsafe.getUnsafe() 来获得Unsafe ,因此需要使用反射的方式拿到它。
我们从Netty中copy一个Permit工具类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public class Permit { public static Unsafe getUnsafe () { return (Unsafe) Permit.ReflectiveStaticField(Unsafe.class,"theUnsafe" ); } public static Object ReflectiveStaticField (Class<?> cls,String fieldName) { try { Field f = cls.getDeclaredField(fieldName); f.setAccessible(true ); return f.get(null ); } catch (NoSuchFieldException | IllegalAccessException e) { return null ; } } public static Field GetField (Class<?> clz,String fieldName) throws NoSuchFieldException{ Class<?> c = clz; Field f = null ; while (c != null ){ try { f = c.getDeclaredField(fieldName); break ; } catch (NoSuchFieldException e) { c = c.getSuperclass(); } } if (f==null ){ throw new NoSuchFieldException (c.getName() + "::" + fieldName); } return f; } }
2.2 判断平台大小端 仿照Bits, 我们使用分配内存,再放入一个特殊的long类型,求出他的低地址,然后判断大小端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public static ByteOrder nativeByteOrder=null ;static { Unsafe unsafe = getUnsafe(); long l = unsafe.allocateMemory(8 ); try { unsafe.putLong(l,0x0102030405060708 ); byte value = unsafe.getByte(l); switch (value){ case 0x01 : { nativeByteOrder = ByteOrder.BIG_ENDIAN; break ; } case 0x08 :{ nativeByteOrder = ByteOrder.LITTLE_ENDIAN; } } }finally { unsafe.freeMemory(l); } }
2.3 使用cas模拟临界区 对于一个volatile变量,如果你只想检查它的状态做判断,那么无需使用原子性。
但是如果想模拟一个临界区,那么必须使用原子的修改操作,来修改状态位。
它通常是用一个 while死循环来模拟,这样的公式代码在juc中随处可见:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 private volatile int state=0 ;private final Unsafe unsafe;private final long STATE_OFFSET;public boolean foo () { int i ; for (;;){ i=state; A: if (unsafe.compareAndSetInt(this ,STATE_OFFSET,0 ,-1 )){ state=1 ; return true ; } } }
值得注意的是: 上面模板 代码”可能”存在死循环状态 。
如果state变为1以后,如果【后续】没有任何其他线程将state其变为0,那么将会死循环。
这个 “可能”想表达的含义是,这么写代码 , 没有语法错误,并且能正常执行,但它可能不符合业务需求。
2.4 顺序put优化volatile 在FutureTask中, state 过渡到终止状态以后,不会再发生改变。并且每一种状态都是独一无二的。
因此,可以使用 putOrderedInte 来优化 putIntVolatile() :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public boolean cancel (boolean mayInterruptIfRunning) { if (!(state == NEW && UNSAFE.compareAndSwapInt(this , stateOffset, NEW, mayInterruptIfRunning ? INTERRUPTING : CANCELLED))) return false ; try { if (mayInterruptIfRunning) { try { Thread t = runner; if (t != null ) t.interrupt(); } finally { UNSAFE.putOrderedInt(this , stateOffset, INTERRUPTED); } } } finally { finishCompletion(); } return true ; }
3. java中的大内存 在java中申请数组,数组最大长度必须小于等于 Integer.MAX_VALUE
在考虑到内存充裕的情况下,常规手段java能申请到多大的内存空间处理数据?
对于 long[] 来说, 最多能够申请到 long[Integer.MAX_VALUE] 约等于16G的数据 8B * (2^31-1) /1024 /1024/1024 = 15.99GB
对于 int[] 来说, 最多能够申请到 iny[Integer.MAX_VALUE] 约等于8G的数据 4B * (2^31-1) /1024 /1024/1024 = 7.99GB
对于 byte[] 来说,最多能够申请到 byte[] 与等于1.9GB 1B * (2^31-1) /1024 /1024/1024 = 1.99GB
也就是说,我们可以通过 “使用long[]申请到超大的内存,然后把它看作byte来处理的方式” 来变相扩大byte数组的分配空间。
在java中想要这样做,需要借助Unsafe 访问数组 :
4. 一些其他的思考 4.1 什么场景下使用原子数组 AtomicIntegerArray保证了get操作的可见性,意味着对数组元素变化是敏感的。
如果某些业务, 严格依赖数组元素当前状态下的值,可能会用到。
1 2 3 4 5 6 7 8 public final int get (int i) { return getRaw(checkedByteOffset(i)); }private int getRaw (long offset) { return unsafe.getIntVolatile(array, offset); }
5. 参考 https://segmentfault.com/a/1190000000441670