谈谈java中的unsafe类 前言 主要方法 Unsafe

这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战

前言

看过JUC并发包里面的源码,就一定明白Unsafe类是整个java并发包底层实现的核心。Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力,Unsafe类提供了硬件级别的原子操作,Unsafe里面的方法都是 native方法,通过使用JNI的方式来访问本地C++实现库。下面就继续深入的看一下Unsafe类。

主要方法

Unsafe有很多public native修饰的方法,还有几十个基于public native方法的其他方法。但是大体可分为以下几类:

(1)初始化操作

(2)操作对象属性

(3)操作数组元素

(4)线程挂起和回复

(5)CAS机制

下面就对Unsafe源码进一步分析。

操作属性方法

1
2
3
vbnet复制代码//通过给定的Java变量获取引用值。这里实际上是获取一个Java对象o中,获取偏移地址为offset的属性的值,此方法可以突破修饰符的抑制,也就是无视private、protected和default修饰符。类似的方法有getInt、getDouble等等。同理还有putObject方法。

public native Object getObject(Object o, long offset);
1
2
3
java复制代码//强制从主存中获取属性值。类似的方法有getIntVolatile、getDoubleVolatile等等。同理还有putObjectVolatile。

public native Object getObjectVolatile(Object o, long offset);
1
2
3
java复制代码//设置o对象中offset偏移地址offset对应的Object型field的值为指定值x。这是一个有序或者有延迟的putObjectVolatile方法,并且不保证值的改变被其他线程立即看到。只有在field被volatile修饰并且期望被修改的时候使用才会生效。类似的方法有putOrderedInt和putOrderedLong。

public native void putOrderedObject(Object o, long offset, Object x);
1
2
3
4
5
6
7
8
java复制代码//返回给定的静态属性在它的类的存储分配中的位置(偏移地址)。
public native long staticFieldOffset(Field f);

//返回给定的非静态属性在它的类的存储分配中的位置(偏移地址)。
public native long objectFieldOffset(Field f);

//返回给定的静态属性的位置,配合staticFieldOffset方法使用。
public native Object staticFieldBase(Field f);

操作数组

1
2
3
4
5
java复制代码//返回数组类型的第一个元素的偏移地址(基础偏移地址)
public native int arrayBaseOffset(Class arrayClass);

//返回数组中元素与元素之间的偏移地址的增量。这两个方法配合使用就可以定位到任何一个元素的地址。
public native int arrayIndexScale(Class arrayClass);

内存管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码//获取本地指针的大小(单位是byte),通常值为4或者8。常量ADDRESS_SIZE就是调用此方法。
public native int addressSize();

//获取本地内存的页数,此值为2的幂次方。
public native int pageSize();

//分配一块新的本地内存,通过bytes指定内存块的大小(单位是byte),返回新开辟的内存的地址
public native long allocateMemory(long bytes);

//通过指定的内存地址address重新调整本地内存块的大小,调整后的内存块大小通过bytes指定(单位为byte)。
public native long reallocateMemory(long address, long bytes);

//将给定内存块中的所有字节设置为固定值(通常是0)。
public native void setMemory(Object o, long offset, long bytes, byte value);

内存屏障

1
2
3
4
5
6
7
8
csharp复制代码//在该方法之前的所有读操作,一定在load屏障之前执行完成。
public native void loadFence();

//在该方法之前的所有写操作,一定在store屏障之前执行完成
public native void storeFence();

//在该方法之前的所有读写操作,一定在full屏障之前执行完成,这个内存屏障相当于上面两个(load屏障和store屏障)的合体功能。
public native void fullFence();

线程挂起和恢复

1
2
3
4
5
java复制代码//释放被park创建的在一个线程上的阻塞。由于其不安全性,因此必须保证线程是存活的
public native void unpark(Object thread);

//阻塞当前线程,一直等道unpark方法被调用
public native void park(boolean isAbsolute, long time);`

CAS机制

Unsafe类中给出了大量比较并设置和比较并交换方法。这些方法都指向某个native的方法作为底层实现。

1
2
3
4
5
java复制代码public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

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操作的时候,将内存位置的值与预期原值比较,如果相匹配,那么处理器会自动将该位置值更新为新值,否则,处理器不做任何操作

Unsafe的使用

获取Unsafe实例

Unsafe 提供了getUnsafe方法,如下所示:

1
2
3
4
5
typescript复制代码public class Main {
public static void main(String[] args) {
Unsafe.getUnsafe();
}
}

直接这样用会报异常:

1
2
3
php复制代码Exception in thread "main" java.lang.SecurityException: Unsafe
at sun.misc.Unsafe.getUnsafe(Unsafe.java:90)
at com.bendcap.java.jvm.unsafe.Main.main(Main.java:13)

因为Unsafe类主要是JDK内部使用,并不提供给普通用户调用。但是仍让可以通过反射获取到实例:

1
2
3
4
5
6
7
8
9
10
11
12
csharp复制代码public static Unsafe testUnsafe() {
try {
Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
Field field = unsafeClass.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
return unsafe;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

改变私有字段

假设有如下类:

1
2
3
4
5
6
7
arduino复制代码public class UnsafeDemo {
private int juejin = 0;

public boolean juejinDisclosed() {
return juejin == 1;
}
}

下面通过Unsafe来改变私有属性juejin的值。

1
2
3
4
5
6
7
8
9
10
ini复制代码UnsafeDemo ud = new UnsafeDemo();
Field field = null;
try {
field = ud.getClass().getDeclaredField("juejin");
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
Unsafe unsafe = testUnsafe();
unsafe.putInt(ud, unsafe.objectFieldOffset(field), 1);
return ud.juejinDisclosed();

通过unsafe.putInt直接改变了ud的私有属性的值。一旦通过反射获得了类的私有属性字段,就可以直接操作它的值。

总结

虽然Unsafe看起来不会被用到,但是能帮助我们更好的理解JUC的并发原理,因此还是很有必要学习的。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%