java Unsafe工具类
Unsafe类是JRE提供的一个工具类,在sun.misc包下。该类提供了一些计算机底层操作工具方法。如直接内存操作,类似指针形式的内存访问,线程调度等。提高了java执行效率,但是如果使用不当,同时也带来了一定的风险。这个类被设计主要供java平台类库使用(像JUC包中大量使用该类),不是供实际应用开发使用,因此虽然其提供了一个单例方法获取类实例,但是会判断当前类是不是被引导类加载,也就是当前类加载器是null。否则会抛出SecurityException异常。
那么怎么获取该类实例用于测试?
Unsafe类中单例方式实例化的对象实例放在theUnsafe属性中,可以通过反射获取该属性。
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
return (Unsafe) f.get(null);
CAS操作
CAS:compare and swap 。比较并交换的意思。就是比如有一个int a。我要修改a的值,我要提供我修改前a的原值,表示我是在这个基础上进行修改的,如果比较当前a和这个值相等,则可以进行swap操作。否则取消swap操作。
相关方法:
objectFieldOffset(Field) 获取属性在类中偏移量
compareAndSwapInt(object,offset,expect,update)
juc中提供的一些原子操作类,基本上都是通过Unsafe中的方法来实现的。compareAndSwapInt需要4个参数
object 对象实例
offset 要修改的属性在对象中的offset值。可以通过objectFieldOffset()获得。对象实例大小和其对应的类大小其实是一样的(如int型的占4个字节,long占8个字节,对象引用类型占4个字节,静态变量不在class里),可以看前面写的一篇文章使用JOL查看类内存结构,所以对于一个类不管哪个实例对象,其内部一个属性的偏移量是固定的。
expect 该属性原值,我的修改基础值(我在什么基础上修改的)如果不是该值,则说明被其它线程修改过,则不符合我的计算逻辑,不会进行swap修改操作
update 要将该属性修改的结果值
测试实例UnSafeTest类,定义一个int型的count变量,使用Unsafe的cas设置该值,在构造函数里读取count在类中的offset值,然后通过定义的cas方法进行count的赋值
public class UnSafeTest {
static Unsafe unsafe;
private int count;
private long countOffset;
public long getCount(){
return count;
}
public UnSafeTest() throws NoSuchFieldException {
long offset = unsafe.objectFieldOffset(UnSafeTest.class.getDeclaredField("count"));
System.out.println("offset:"+offset);
countOffset = offset;
}
public static void main(String[] args) throws Exception {
unsafe = getInstance1();
UnSafeTest test = new UnSafeTest();
System.out.println(test.cas(0,100));
System.out.println(test.getCount());
//设置成功,输出count值100
}
/**
* cas设置字段值
*/
boolean cas(int expect,int update) {
return unsafe.compareAndSwapInt(this,countOffset,expect,update);
}
/**
* 使用反射获取实例
*/
static Unsafe getInstance1() throws NoSuchFieldException, IllegalAccessException {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
return (Unsafe) f.get(null);
}
}
Unsafe类提供了compareAndSwapInt、compareAndSwapLong、compareAndSwapObject三个cas操作方法。另外对于8大基本类型和引用类型都提供了对应的put和get方法,都是通过属性对应的offset值进行读写操作。offset感觉和指针有的一拼。
获取类实例
可以通过allocateInstance()方法获取类实例,但是这种方法获取的实例不会执行构造函数。
UnSafeTest t = (UnSafeTest) unsafe.allocateInstance(UnSafeTest.class);
System.out.println("initInstance:"+t.countOffset);
线程阻塞与唤醒
park和unpark方法阻塞和唤醒线程
Thread t1 = new Thread(()->{
System.out.println("T1 run");
//T1 挂起等待被唤醒
unsafe.park(false,0l);
System.out.println(System.currentTimeMillis()+ ":T1 end");
});
Thread t2 = new Thread(()->{
System.out.println("T2 run");
System.out.println(System.currentTimeMillis()+":T2 end");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//唤醒T2线程
unsafe.unpark(t1);
});
t1.start();
Thread.sleep(2000);
t2.start();
内存分配
可以直接使用allocateMemory()分配内存,使用的内存是堆外内存,不会被gc自动回收,要自己通过freeMemory()方法处理。据说DirectByteBuffer就是使用的Unsafe分配的内存。没使用过也不知道有哪些应用场景不做过多介绍。使用也应该是堆一些大对象IO操作才会用到吧。
内存屏障
还有在volatile学习的时候一直说的内存屏障的概念,在这里可以通过unsafe类显示的添加内存屏障。
unsafe.loadFence();
unsafe.fullFence();
unsafe.storeFence();
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)