java:Unsafe

java:Unsafe

1 前言

底层的源码中,经常可见Unsafe,Unsafe类是java用于内存管理的类,但操作不当,可能导致内存泄漏等问题,官方亦不推荐过多使用Unsafe,故而名为Unsafe。

2 简单使用

观察Unsafe代码可知,Unsafe的实例对象theUnsafe是static静态代码块初始化,且构造方法私有,仅提供getUnsafe()供外部调用,但是getUnsafe()中会判断!VM.isSystemDomainLoader(var0.getClassLoader())满足时,将抛出new SecurityException(“Unsafe”)异常。

private static final Unsafe theUnsafe;

private Unsafe() {
}

@CallerSensitive
public static Unsafe getUnsafe() {
    Class var0 = Reflection.getCallerClass();
    if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
        throw new SecurityException("Unsafe");
    } else {
        return theUnsafe;
    }
}

static {
    registerNatives();
    Reflection.registerMethodsToFilter(Unsafe.class, new String[]{"getUnsafe"});
    theUnsafe = new Unsafe();
    ARRAY_BOOLEAN_BASE_OFFSET = theUnsafe.arrayBaseOffset(boolean[].class);
    ARRAY_BYTE_BASE_OFFSET = theUnsafe.arrayBaseOffset(byte[].class);
    ARRAY_SHORT_BASE_OFFSET = theUnsafe.arrayBaseOffset(short[].class);
    ARRAY_CHAR_BASE_OFFSET = theUnsafe.arrayBaseOffset(char[].class);
    ARRAY_INT_BASE_OFFSET = theUnsafe.arrayBaseOffset(int[].class);
    ARRAY_LONG_BASE_OFFSET = theUnsafe.arrayBaseOffset(long[].class);
    ARRAY_FLOAT_BASE_OFFSET = theUnsafe.arrayBaseOffset(float[].class);
    ARRAY_DOUBLE_BASE_OFFSET = theUnsafe.arrayBaseOffset(double[].class);
    ARRAY_OBJECT_BASE_OFFSET = theUnsafe.arrayBaseOffset(Object[].class);
    ARRAY_BOOLEAN_INDEX_SCALE = theUnsafe.arrayIndexScale(boolean[].class);
    ARRAY_BYTE_INDEX_SCALE = theUnsafe.arrayIndexScale(byte[].class);
    ARRAY_SHORT_INDEX_SCALE = theUnsafe.arrayIndexScale(short[].class);
    ARRAY_CHAR_INDEX_SCALE = theUnsafe.arrayIndexScale(char[].class);
    ARRAY_INT_INDEX_SCALE = theUnsafe.arrayIndexScale(int[].class);
    ARRAY_LONG_INDEX_SCALE = theUnsafe.arrayIndexScale(long[].class);
    ARRAY_FLOAT_INDEX_SCALE = theUnsafe.arrayIndexScale(float[].class);
    ARRAY_DOUBLE_INDEX_SCALE = theUnsafe.arrayIndexScale(double[].class);
    ARRAY_OBJECT_INDEX_SCALE = theUnsafe.arrayIndexScale(Object[].class);
    ADDRESS_SIZE = theUnsafe.addressSize();
}

判断是否为最顶层的类加载器:

public static boolean isSystemDomainLoader(ClassLoader var0) {
    return var0 == null;
}

在双亲委派机制中,我们知道,最顶层的类加载器,就是Bootstrap ClassLoader,而上面判断非最顶层类加载器将抛错,如下简单演示下类加载器:

package com.xiaoxu.lock;

/**
 * @author xiaoxu
 * @date 2022-09-29
 * spring_boot:com.xiaoxu.lock.Shuangqing
 */
public class Shuangqing {
    public static void main(String[] args) {
        //  Application ClassLoader  应用程序类加载器(上层为扩展类加载器)
        ClassLoader classLoader = Shuangqing.class.getClassLoader();
        System.out.println(classLoader);
        //  Extension ClassLoader  扩展类加载器(第二层,上层为启动类加载器)
        System.out.println(classLoader.getParent());
        // Bootstrap ClassLoader   启动类加载器(最顶层)
        System.out.println(classLoader.getParent().getParent());
    }
}

执行结果如下,可知最顶层的Bootstrap ClassLoader加载器返回的是null:

sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@7cd84586
null

如果我们直接在我们的应用代码里,使用Unsafe.getUnsafe()会报错SecurityException(“Unsafe”)。

在这里插入图片描述

执行报错:

在这里插入图片描述
可尝试反射获取theUnsafe,并演示部分常用的api,如staticFieldOffset、objectFieldOffset、compareAndSwapInt:

package com.xiaoxu.lock;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import sun.misc.Unsafe;

import java.lang.reflect.Field;

/**
 * @author xiaoxu
 * @date 2022-09-29
 * spring_boot:com.xiaoxu.lock.UnsafeUse
 */
public class UnsafeUse {
    private int a;
    private int b;
    private static final int c;
    static {
        c = 300;
    }

    public int getA() {
        return a;
    }

//    public void setA(int a) {
//        this.a = a;
//    }

    public int getB() {
        return b;
    }

//    public void setB(int b) {
//        this.b = b;
//    }

    public static Unsafe getUnsafe(){
        Unsafe unsafe = null;
        try{
            Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafeField.setAccessible(true);
            // 反射获取static类型的成员变量(field.get(null)获取static变量,实例成员变量要传入实例对象)
            unsafe = (Unsafe) theUnsafeField.get(null);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            System.out.println(e.getMessage());
        }
        return unsafe;
    }

    @Override
    public String toString(){
        return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
    }

    public static void useMe(){
        Unsafe unsafe = getUnsafe();
        System.out.println(unsafe);
        UnsafeUse use = new UnsafeUse();
        System.out.println(use);
        // unsafe.objectFieldOffset针对实例成员变量,unsafe.staticFieldOffset针对static变量
        try {
            System.out.println("c:"+UnsafeUse.c);
            // 注意:compareAndSwapInt修改静态成员变量的值时,传入类.class即可
            unsafe.compareAndSwapInt(UnsafeUse.class,unsafe.staticFieldOffset(UnsafeUse.class.getDeclaredField("c")),
                    300,444);
            System.out.println("修改后的c:"+UnsafeUse.c);
            long valueOffset = unsafe.objectFieldOffset(UnsafeUse.class.getDeclaredField("b"));
            System.out.println("b的内存偏移地址:"+valueOffset);
            System.out.println("b的初始值:"+use.getB());
            // compareAndSwapInt:第1个参数是实例对象,第2个参数是实例对象的实例成员变量b的内存偏移值,
            // 第3个参数是实例对象的b现在的值,第4个参数是实例对象的b希望update的值,
            // 如果第3个参数==实例对象的b现在的值,才更新b的值为第4个参数
            unsafe.compareAndSwapInt(use,valueOffset,0,177);
            unsafe.compareAndSwapInt(use,valueOffset,177,172);
            // compare比较失败,当前的b是172,不是171,更新失败,b仍为172
            //  CAS更新的方式,不依赖于set方法!! CAS是底层操作数据的方式
            unsafe.compareAndSwapInt(use,valueOffset,171,11);
            System.out.println("修改后的b:"+use.getB());
        } catch (NoSuchFieldException e) {
            System.out.println("获取b失败");
        }
    }

    public static void main(String[] args) {
        useMe();
    }
}

执行结果如下:

sun.misc.Unsafe@30dae81
UnsafeUse[a=0,b=0]
c:300
修改后的c:444
b的内存偏移地址:16
b的初始值:0
修改后的b:172

且compareAndSwapInt的更新是安全可靠的(具有原子性),故而java并发编程的JUC(java.util.concurrent)工具包的底层源码中,常见Unsafe的使用。

3 其他常用方法

3.1 unsafe.putOrderedObject:

public native void putOrderedObject(Object var1, long var2, Object var4)

unsafe.putOrderedObject本质上就是替换对象var1偏移位置是var2处的对象为var4,具有禁止指令重排特性,但不具有可见性。

针对可见性,这里先提前引入一些概念,在java中,变量都是存储在主内存中(包括多个线程共享的变量),对于共享变量,线程不是直接去操作主内存中的变量,而是每个线程单独存在一个工作内存,线程会拷贝主内存中的变量作为副本放入到工作内存中,实际线程是操作工作内存中的变量的副本,所以就可能出现"脏读"的现象,比如共享变量5,线程A读取共享变量5,其工作内存是5,修改其为6,但是此时还没刷新到主内存中,此时线程B读取共享变量,那么读取到的变量就是主内存中的5,而不是6,出现脏读,这里线程A修改共享变量5没有立马刷新到主内存中,即可视为不可见性。

解决可见性的方式,有volatile关键字。java中的volatile关键字具有可见性和禁止指令重排的特性,但是不具有原子性(故而高并发下使用volatile,需要考虑加锁),而可见性就是指,当写一个volatile变量时,JMM(java内存模型)会把该线程对应的本地内存中的共享变量值,立即刷新到主内存中,当读一个volatile变量时,JMM(java内存模型)会把该线程对应的本地内存设置为无效,重新回主内存中读取,但是volatile会造成漏写,是非原子性的,需加锁。

如下示例说明unsafe.putOrderedObject是不具有可见性的,以及其简单的功能使用:

package com.xiaoxu.lock;

import org.springframework.objenesis.instantiator.util.UnsafeUtils;
import sun.misc.Unsafe;

import java.util.concurrent.TimeUnit;

/**
 * @author xiaoxu
 * @date 2022-10-06 19:25
 * spring_boot:com.xiaoxu.lock.UnsafeUse2
 */
public class UnsafeUse2 {
    //    volitale:具有可见性和指令禁止重排性质,没有原子性
    //    static volatile boolean flag = true;

    // 如果是   static boolean flag = true; unsafe.putOrderedObject将无法修改flag值
    // static volatile Boolean flag = true; 具有可见性和禁止指令重排,搭配   unsafe.putOrderedObject
    // 就可以达到替换对象的同时,还具有可见性
    static Boolean flag = true;

    static Unsafe unsafe;
    static long FLAG;
    static {
        // spring-core-5.3.9中有封装好的unsafe的工具类
        try {
            unsafe = UnsafeUtils.getUnsafe();
            FLAG = unsafe.staticFieldOffset(UnsafeUse2.class.getDeclaredField("flag"));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static void testOne(){
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"开始执行");
            while(flag){}
            System.out.println(Thread.currentThread().getName()+"结束执行");
        },"xiaoxu1").start();
        try {
            TimeUnit.MILLISECONDS.sleep(30);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        flag = false;
        System.out.println("main线程修改完了flag");
    }

    public static void testOneByPutOrdered(){
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"开始执行");
            while(flag){}
            System.out.println(Thread.currentThread().getName()+"结束执行");
        },"xiaoxu1").start();
        try {
            TimeUnit.MILLISECONDS.sleep(30);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
//        unsafe.putOrderedObject具有禁止指令重排的效果,但是不具有可见性
        unsafe.putOrderedObject(UnsafeUse2.class,FLAG,false);
//        flag = false;
        System.out.println("修改后的UnsafeUse2的flag:"+flag);
        System.out.println("测试putOrderedObject不具有可见性,main线程修改完了flag");
    }


    public static void main(String[] args) {
        testOne();
//        testOneByPutOrdered();
    }
}

执行testOne(),可见因为static变量值flag不是可见性的,没有立马回写到主内存中,即便main线程修改了flag为false,线程还是在执行while循环,因为只是线程的工作内存修改了值,但是主内存中实际还未刷新:

在这里插入图片描述

上述代码修改变量flag为如下,即可立即回写到主内存中:

static volatile Boolean flag = true;

再次执行:

在这里插入图片描述

为了证明unsafe.putOrderedObject不具有可见性,以及演示下功能使用,还是保留如下:

static Boolean flag = true;

再次执行testOneByPutOrdered():

在这里插入图片描述

可见unsafe.putOrderedObject已经将flag修改为了false,但是实际线程还是在while循环里(仅main主线程可见,而其他子线程不可见),故而其是不可见的。

故而高并发中,需要保证可见性的同时更改对象,建议unsafe.putOrderedObject和volatile关键字一起使用(当然还需要考虑加锁),部分源码中可见,如CompletableFuture:

abstract static class Completion extends ForkJoinTask<Void>
    implements Runnable, AsynchronousCompletionTask {
    volatile Completion next;      // Treiber stack link

    /**
     * Performs completion action if triggered, returning a
     * dependent that may need propagation, if one exists.
     *
     * @param mode SYNC, ASYNC, or NESTED
     */
    abstract CompletableFuture<?> tryFire(int mode);

    /** Returns true if possibly still triggerable. Used by cleanStack. */
    abstract boolean isLive();

    public final void run()                { tryFire(ASYNC); }
    public final boolean exec()            { tryFire(ASYNC); return true; }
    public final Void getRawResult()       { return null; }
    public final void setRawResult(Void v) {}
}

static void lazySetNext(Completion c, Completion next) {
    UNSAFE.putOrderedObject(c, NEXT, next);
}

因为UNSAFE.putOrderedObject是不可见性的,所以方法名为lazySetNext(是延迟回写到主内存中的),但是性能较高,结合较轻的volatile关键字使用效果很好(而不是一味的使用较重的synchronized来达到效果,synchronized也能达到可见性,但是可能造成线程阻塞,较重。《深入理解java虚拟机》一书中提到,对1个变量执行unlock前,必须先将此变量同步回主内存中,所以加锁、释放锁也是可见性的,故而synchronized、ReentrantLock等等的加锁释放锁操作,是可见的)。源码中UNSAFE.putOrderedObject(c, NEXT, next);的偏移地址NEXT,实际上就是取的Completion的volatile Completion next变量,为了保证数据的可见性,故而加上了volatile关键字保证可见性。

3.2 unsafe.putOrderedInt和unsafe.putOrderedLong:

java中没有C语言的指针概念,故而引入一个后门Unsafe工具类,用于管理内存、操作变量等等。其中unsafe.putOrderedInt和unsafe.putOrderedLong,可以达到为数组中赋值的效果。

Unsafe源码方法如下:

public native void putOrderedInt(Object var1, long var2, int var4);

public native void putOrderedLong(Object var1, long var2, long var4);

提到这两个方法使用前,需要引入别的一些东西,如下:

在Unsafe的源码中,有如下数值:

static {
    registerNatives();
    Reflection.registerMethodsToFilter(Unsafe.class, new String[]{"getUnsafe"});
    theUnsafe = new Unsafe();
    ARRAY_BOOLEAN_BASE_OFFSET = theUnsafe.arrayBaseOffset(boolean[].class);
    ARRAY_BYTE_BASE_OFFSET = theUnsafe.arrayBaseOffset(byte[].class);
    ARRAY_SHORT_BASE_OFFSET = theUnsafe.arrayBaseOffset(short[].class);
    ARRAY_CHAR_BASE_OFFSET = theUnsafe.arrayBaseOffset(char[].class);
    ARRAY_INT_BASE_OFFSET = theUnsafe.arrayBaseOffset(int[].class);
    ARRAY_LONG_BASE_OFFSET = theUnsafe.arrayBaseOffset(long[].class);
    ARRAY_FLOAT_BASE_OFFSET = theUnsafe.arrayBaseOffset(float[].class);
    ARRAY_DOUBLE_BASE_OFFSET = theUnsafe.arrayBaseOffset(double[].class);
    ARRAY_OBJECT_BASE_OFFSET = theUnsafe.arrayBaseOffset(Object[].class);
    ARRAY_BOOLEAN_INDEX_SCALE = theUnsafe.arrayIndexScale(boolean[].class);
    ARRAY_BYTE_INDEX_SCALE = theUnsafe.arrayIndexScale(byte[].class);
    ARRAY_SHORT_INDEX_SCALE = theUnsafe.arrayIndexScale(short[].class);
    ARRAY_CHAR_INDEX_SCALE = theUnsafe.arrayIndexScale(char[].class);
    ARRAY_INT_INDEX_SCALE = theUnsafe.arrayIndexScale(int[].class);
    ARRAY_LONG_INDEX_SCALE = theUnsafe.arrayIndexScale(long[].class);
    ARRAY_FLOAT_INDEX_SCALE = theUnsafe.arrayIndexScale(float[].class);
    ARRAY_DOUBLE_INDEX_SCALE = theUnsafe.arrayIndexScale(double[].class);
    ARRAY_OBJECT_INDEX_SCALE = theUnsafe.arrayIndexScale(Object[].class);
    ADDRESS_SIZE = theUnsafe.addressSize();
}

如theUnsafe.arrayBaseOffset(int[].class),实际上就是指的int数组的偏移地址,然后theUnsafe.arrayIndexScale(int[].class),就是指的int数据,每个数据所占据的偏移大小,即字节大小,我们知道,int类型是4个字节,而long是8个字节,如下所示:

package com.xiaoxu.lock;

import org.springframework.objenesis.instantiator.util.UnsafeUtils;
import sun.misc.Unsafe;

/**
 * @author xiaoxu
 * @date 2022-10-06 21:03
 * spring_boot:com.xiaoxu.lock.UnsafeUse3
 */
public class UnsafeUse3 {
    static Unsafe unsafe;

    static {
        // spring-core-5.3.9中有封装好的unsafe的工具类
        try {
            unsafe = UnsafeUtils.getUnsafe();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        // int4个字节,long
        int intArrayOffset = unsafe.arrayBaseOffset(int[].class);
        System.out.println("int数组的偏移地址:"+intArrayOffset);
        int k = unsafe.arrayIndexScale(int[].class);
        System.out.println("int变量的大小:"+k);
        int longArrayOffset = unsafe.arrayBaseOffset(long[].class);
        System.out.println("long数组的偏移地址:"+longArrayOffset);
        int long1 = unsafe.arrayIndexScale(long[].class);
        System.out.println("long变量的大小:"+long1);
    }
}

执行结果如下:

int数组的偏移地址:16
int变量的大小:4
long数组的偏移地址:16
long变量的大小:8

可见Unsafe类获取的到int、long的大小,是符合预期的,其他的同理。

另外在《深入理解java虚拟机》一书的对象内存布局中提到,HotSpot虚拟机(是Sun/OracleJDK和OpenJDK中默认的java虚拟机)中,对象在堆内存中的存储布局可划分成三个部分:对象头(Header)、实例数据(Instance data)和对齐填充(padding)。

想查看变量的内存布局,可以使用如下工具:

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.9</version>
</dependency>

修改代码如下:

package com.xiaoxu.lock;

import org.openjdk.jol.info.ClassLayout;
import org.springframework.objenesis.instantiator.util.UnsafeUtils;
import sun.misc.Unsafe;

/**
 * @author xiaoxu
 * @date 2022-10-06 21:03
 * spring_boot:com.xiaoxu.lock.UnsafeUse3
 */
public class UnsafeUse3 {
    static Unsafe unsafe;

    static {
        // spring-core-5.3.9中有封装好的unsafe的工具类
        try {
            unsafe = UnsafeUtils.getUnsafe();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static void test(){
        int[] x = new int[3];
        System.out.println(ClassLayout.parseInstance(x).toPrintable());
        int[] y = new int[5];
        System.out.println(ClassLayout.parseInstance(y).toPrintable());
    }

    public static void main(String[] args) {
        // int4个字节,long
        int intArrayOffset = unsafe.arrayBaseOffset(int[].class);
        System.out.println("int数组的偏移地址:"+intArrayOffset);
        int k = unsafe.arrayIndexScale(int[].class);
        System.out.println("int变量的大小:"+k);
        int longArrayOffset = unsafe.arrayBaseOffset(long[].class);
        System.out.println("long数组的偏移地址:"+longArrayOffset);
        int long1 = unsafe.arrayIndexScale(long[].class);
        System.out.println("long变量的大小:"+long1);
        test();
    }
}

执行结果:

int数组的偏移地址:16
int变量的大小:4
long数组的偏移地址:16
long变量的大小:8
[I object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           6d 01 00 f8 (01101101 00000001 00000000 11111000) (-134217363)
     12     4        (object header)                           03 00 00 00 (00000011 00000000 00000000 00000000) (3)
     16    12    int [I.<elements>                             N/A
     28     4        (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

[I object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           6d 01 00 f8 (01101101 00000001 00000000 11111000) (-134217363)
     12     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
     16    20    int [I.<elements>                             N/A
     36     4        (loss due to the next object alignment)
Instance size: 40 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

在这里插入图片描述

说到对齐填充,这个不是必须有的,《深入理解java虚拟机》中还提到,HotSpot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,也就是说任何对象的大小都必须是8字节的整数倍,所以上述new int[3]因为对象头和实例数据是28,+4才是32(4*8),所以对齐填充为4,其余同理,如果对象头和实例数据刚好是8的整数倍,那么就没有对齐填充。

介于上述说明,那么操作数组就显而易见了。我们知道数组是有首地址的,也就是上述所说的对象头,数组元素则位于实例数据处,根据大小作偏移即可。由此可有下述操作:

package com.xiaoxu.lock;

import org.openjdk.jol.info.ClassLayout;
import org.springframework.objenesis.instantiator.util.UnsafeUtils;
import sun.misc.Unsafe;

import java.util.Arrays;

/**
 * @author xiaoxu
 * @date 2022-10-06 21:03
 * spring_boot:com.xiaoxu.lock.UnsafeUse3
 */
public class UnsafeUse3 {
    static Unsafe unsafe;

    static {
        // spring-core-5.3.9中有封装好的unsafe的工具类
        try {
            unsafe = UnsafeUtils.getUnsafe();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static void test(){
        int[] x = new int[3];
        System.out.println(ClassLayout.parseInstance(x).toPrintable());
        int[] y = new int[5];
        System.out.println(ClassLayout.parseInstance(y).toPrintable());
        System.out.println("unsafe获取的数组首地址:"+unsafe.arrayBaseOffset(int[].class)
                +";ClassLayout观察所得int数组的首地址:16;两者一致.");
        // 比如x,希望向索引为1的位置插入数据,那么偏移1个单位的int字节长度即可
        // 如果是为x[0]插入数据,在首地址的16偏移位置(起始位置)插入即可
        unsafe.putOrderedInt(x,16+4*2,355);
        unsafe.putOrderedInt(x,16+4*2,125);
        unsafe.putOrderedInt(x,16,999);
        // 同理,为x[1]插入数据
        unsafe.putOrderedInt(x,16+4,777);
        System.out.println(Arrays.toString(x));
    }

    public static void main(String[] args) {
        // int4个字节,long
        int intArrayOffset = unsafe.arrayBaseOffset(int[].class);
        System.out.println("int数组的偏移地址:"+intArrayOffset);
        int k = unsafe.arrayIndexScale(int[].class);
        System.out.println("int变量的大小:"+k);
        int longArrayOffset = unsafe.arrayBaseOffset(long[].class);
        System.out.println("long数组的偏移地址:"+longArrayOffset);
        int long1 = unsafe.arrayIndexScale(long[].class);
        System.out.println("long变量的大小:"+long1);
        test();
    }
}

执行结果如下:

int数组的偏移地址:16
int变量的大小:4
long数组的偏移地址:16
long变量的大小:8
[I object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           6d 01 00 f8 (01101101 00000001 00000000 11111000) (-134217363)
     12     4        (object header)                           03 00 00 00 (00000011 00000000 00000000 00000000) (3)
     16    12    int [I.<elements>                             N/A
     28     4        (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

[I object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           6d 01 00 f8 (01101101 00000001 00000000 11111000) (-134217363)
     12     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
     16    20    int [I.<elements>                             N/A
     36     4        (loss due to the next object alignment)
Instance size: 40 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

unsafe获取的数组首地址:16;ClassLayout观察所得int数组的首地址:16;两者一致.
[999, 777, 125]

unsafe.putOrderedLong同理。

3.3 unsafe.getAndSetObject:

unsafe的getAndSetObject,针对的是引用类型的对象(非primitive类型,如int等不使用该方法),返回值是未修改前的引用值,然后执行set操作,故而名为getAndSetObject。

源码示例(AtomicReference):

@SuppressWarnings("unchecked")
public final V getAndSet(V newValue) {
    return (V)unsafe.getAndSetObject(this, valueOffset, newValue);
}

实际使用如下所示:

package com.xiaoxu.lock;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import sun.misc.Unsafe;

import java.lang.reflect.Field;

/**
 * @author xiaoxu
 * @date 2022-09-29
 * spring_boot:com.xiaoxu.lock.UnsafeUse
 */
public class UnsafeUse {
    private int a;
    private int b;
    volatile private Integer d;
    private static final int c;
    static {
        c = 300;
    }

    public int getA() {
        return a;
    }

//    public void setA(int a) {
//        this.a = a;
//    }

    public int getB() {
        return b;
    }

//    public void setB(int b) {
//        this.b = b;
//    }

//    public Integer getD() {
//        return d;
//    }
//
//    public void setD(Integer d) {
//        this.d = d;
//    }

    public static Unsafe getUnsafe(){
        Unsafe unsafe = null;
        try{
            Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafeField.setAccessible(true);
            // 反射获取static类型的成员变量(field.get(null)获取static变量,实例成员变量要传入实例对象)
            unsafe = (Unsafe) theUnsafeField.get(null);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            System.out.println(e.getMessage());
        }
        return unsafe;
    }

    @Override
    public String toString(){
        return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
    }

    public static void getAndSetObj(){
        Unsafe unsafe = getUnsafe();
        long aOffset;
        long dOffset;
        try {
            aOffset = unsafe.objectFieldOffset(UnsafeUse.class.getDeclaredField("a"));
            dOffset = unsafe.objectFieldOffset(UnsafeUse.class.getDeclaredField("d"));
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        }
        UnsafeUse instance = new UnsafeUse();
        System.out.println("初始对象:"+instance+";hash:"+instance.hashCode());
        UnsafeUse instance2 = new UnsafeUse();
        unsafe.compareAndSwapInt(instance2,aOffset,0,188);
        System.out.println("需要替换的对象:"+instance2+";hash:"+instance2.hashCode());
        // a虽然是int类型,非引用类型,但是  unsafe.getAndSetObject(instance, aOffset, 9988) 依然返回的是null
        System.out.println(unsafe.getAndSetObject(instance, aOffset, 9988));
        // unsafe.getAndSetObject:仅针对引用类型使用,非引用使用有误
        System.out.println("初始对象被替换成:"+instance+";hash:"+instance.hashCode());
        System.out.println("*************************");
        UnsafeUse instance3 = new UnsafeUse();
        System.out.println("初始对象:"+instance3+";hash:"+instance3.hashCode());
        UnsafeUse instance4 = new UnsafeUse();
        // compareAndSwapObject:引用型的Integer使用,int不能使用;compareAndSwapInt:int使用,引用型的Integer不能使用
        unsafe.compareAndSwapObject(instance4,dOffset,null,776);
        System.out.println("需要替换的对象:"+instance4+";hash:"+instance4.hashCode());
        Integer number = (Integer)unsafe.getAndSetObject(instance3, dOffset, 9977);
        // 因为是引用类型,且没有赋值,所以初始是null (number)
        System.out.println(number);
        Object andSetObject = unsafe.getAndSetObject(instance3, dOffset, 9933);
        // getAndSetObject:先返回初始值,再赋值,所以如下打印的是 :obj:9977
        System.out.println("obj:"+andSetObject);
        Integer y = (Integer) unsafe.getAndSetObject(instance3, dOffset, 9944);
        // 如下打印9933,但是最终的 instance3:d==9944
        System.out.println(y);
        System.out.println("目前的初始对象:"+instance3+";hash:"+instance3.hashCode());
    }

    public static void useMe(){
        Unsafe unsafe = getUnsafe();
        System.out.println(unsafe);
        UnsafeUse use = new UnsafeUse();
        System.out.println(use);
        // unsafe.objectFieldOffset针对实例成员变量,unsafe.staticFieldOffset针对static变量
        try {
            System.out.println("c:"+UnsafeUse.c);
            // 注意:compareAndSwapInt修改静态成员变量的值时,传入类.class即可
            unsafe.compareAndSwapInt(UnsafeUse.class,unsafe.staticFieldOffset(UnsafeUse.class.getDeclaredField("c")),
                    300,444);
            System.out.println("修改后的c:"+UnsafeUse.c);
            long valueOffset = unsafe.objectFieldOffset(UnsafeUse.class.getDeclaredField("b"));
            System.out.println("b的内存偏移地址:"+valueOffset);
            System.out.println("b的初始值:"+use.getB());
            // compareAndSwapInt:第1个参数是实例对象,第2个参数是实例对象的实例成员变量b的内存偏移值,
            // 第3个参数是实例对象的b现在的值,第4个参数是实例对象的b希望update的值,
            // 如果第3个参数==实例对象的b现在的值,才更新b的值为第4个参数
            unsafe.compareAndSwapInt(use,valueOffset,0,177);
            unsafe.compareAndSwapInt(use,valueOffset,177,172);
            // compare比较失败,当前的b是172,不是171,更新失败,b仍为172
            //  CAS更新的方式,不依赖于set方法!! CAS是底层操作数据的方式
            unsafe.compareAndSwapInt(use,valueOffset,171,11);
            System.out.println("修改后的b:"+use.getB());
        } catch (NoSuchFieldException e) {
            System.out.println("获取b失败");
        }
    }

    public static void main(String[] args) {
        getAndSetObj();
    }
}

执行效果如下:

初始对象:UnsafeUse[a=0,b=0,d=<null>];hash:1232367853
需要替换的对象:UnsafeUse[a=188,b=0,d=<null>];hash:1789447862
null
初始对象被替换成:UnsafeUse[a=-696965424,b=0,d=<null>];hash:1232367853
*************************
初始对象:UnsafeUse[a=0,b=0,d=<null>];hash:38997010
需要替换的对象:UnsafeUse[a=0,b=0,d=776];hash:1942406066
null
obj:9977
9933
目前的初始对象:UnsafeUse[a=0,b=0,d=9944];hash:38997010
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值