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();
posted @ 2023-06-14 16:20  朋羽  阅读(26)  评论(0编辑  收藏  举报