在开始编写文章前,有几个问题需要思考一下:
- Unsafe 类到底是什么?
- Unsafe 具有哪些职责?
- 如何使用 Unsafe 类?
1. Unsafe 类到底是什么?
Java 语言不像 C、C++那样可以直接访问底层操作系统,但是 JVM 为我们提供了一个后门,这个后门就是 Unsafe。Unsafe 为我们提供了硬件级别的原子操作。
2. Unsafe 类具有哪些职责?
2.1 通过 Unsafe 可以分配内存、重新分配内存、释放内存:
public native long allocateMemory(long var1);
public native long reallocateMemory(long var1, long var3);
public native void freeMemory(long var1);
2.2 可以定位对象某字段的内存位置,也可以修改某字段的值,即使是私有变量。
public native long staticFieldOffset(Field var1);
public native long objectFieldOffset(Field var1);
public native int arrayBaseOffset(Class<?> var1);
包含类变量、实例变量和数组的偏移量。数组的如下所示:
public native int arrayBaseOffset(Class<?> var1);
public native int arrayIndexScale(Class<?> var1);
ARRAY_BOOLEAN_BASE_OFFSET = theUnsafe.arrayBaseOffset(boolean[].class);
ARRAY_BYTE_BASE_OFFSET = theUnsafe.arrayBaseOffset(byte[].class);
......
ARRAY_BOOLEAN_INDEX_SCALE = theUnsafe.arrayIndexScale(boolean[].class);
ARRAY_BYTE_INDEX_SCALE = theUnsafe.arrayIndexScale(byte[].class);
......
数组主要涉及到的是数组第一个元素的偏移量和数组中每个元素占用的字节数。在 Unsafe 类中有很多以 BASE_OFFSET 和 INDEX_SCALE 后缀结尾的来常量,这是用来标识上面提到的两个数组因素。
2.3 挂起与恢复
将一个线程挂起是通过调用 park 方法来实现,调用 park 后,线程一直阻塞直到超时或者被中断的条件出现等。unpark 可以终止一个挂起的线程,使其恢复运行。整个并发框架中对线程的挂起封装到 LockSupport 类中:
public class LockSupport {
public static void park() {
UNSAFE.park(false, 0L);
}
public static void parkNanos(long nanos) {
if (nanos > 0)
UNSAFE.park(false, nanos);
}
public static void parkUntil(long deadline) {
UNSAFE.park(true, deadline);
}
public static void parkUntil(Object blocker, long deadline) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(true, deadline);
setBlocker(t, null);
}
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
}
2.4 CAS 操作
通过 CompareAndSwapXXX 执行对变量的原子操作。
/**
* @param obj 需要更新的对象
* @param offset obj中整形field的偏移量
* @param expect 希望field中存在的值
* @param update 如果期待值expect与field的当前值相同,设置field的值为这个新值
* @return 如果field的值被更新返回true
**/
public final native boolean compareAndSwapObject(Object obj, long offset, Object expect, Object update);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
CAS 机制当中使用了 3 个基本操作数:内存地址 V,旧的预期值 A,要修改的新值 B。
3. 如何使用 Unsafe 类?
获取 Unsafe 实例:
public static Unsafe getUnsafeInstance() throws Exception{
Field unsafeStaticField =
Unsafe.class.getDeclaredField("theUnsafe");
unsafeStaticField.setAccessible(true);
return (Unsafe) unsafeStaticField.get(Unsafe.class);
}
读取和修改实例变量的值
public class USTest {
public int intfield ;
public static void main(String[] args) throws Exception {
Unsafe u = getUnsafeInstance();
USTest usTest = (USTest) u.allocateInstance(USTest.class);
long b1 = u.objectFieldOffset(client.class.getDeclaredField("intfield"));
u.putInt(usTest, b1, 2);
}
}
修改静态变量
public class USTest {
public static int staticIntField;
public static void main(String[] args) throws Exception {
Unsafe u = getUnsafeInstance();
Field staticIntField = USTest.class.getDeclaredField("staticIntField");
Object o = u.staticFieldBase(staticIntField);
long b1 = u.staticFieldOffset(staticIntField);
u.putInt(o, b1,5);
}
}
- staticFieldBase 方法获取静态变量所属的类在方法区的首地址,也就是返回 USTest.class 对象
修改和读取数组中的值
public class USTest {
public static int[] arr = {1,2,4,6,7};
public static void main(String[] args) throws Exception {
Unsafe u = getUnsafeInstance();
long a1 = u.arrayBaseOffset(int[].class);
int s1 = u.arrayIndexScale(int[].class);
u.putInt(arr, a1 + s1 * 2,9);
}
}
把数组的的三个值修改为 9。