Random(三)反射的高效替代方案,Unsafe类

1.背景简介

随机数的产生需要访问 Thread 的 threadLocalRandomSeed 等成员变量,但是考虑到类的封装性,这些成员却是包内可见的。

很不幸,ThreadLocalRandom 位于 java.util.concurrent 包,而 Thread 则位于 java.lang 包,因此,ThreadLoalRandom 并没有办法访问 Thread 的 threadLocalRandomSeed 等成员变量。

这时,Java 老鸟们可能会跳出来说:这算什么,看我的反射大法,不管啥都能抠出来访问一下。说的不错,反射是一种可以绕过封装,直接访问对象内存数据的方法,但是,反射的性能不太好,并不适合作为一个高新能的解决方案。

有没有什么办法可以让 ThreadLocalRandom 访问 Thread 的内部成员,同时又具有远超于反射的,且无限接近于直接访问变量的方法呢?答案是肯定的,这就是使用 Unsafe 类。

2.Unsafe类

Unsafe类: 在 sun.misc 包下,提供了硬件级别的原子操作,不属于 Java 标准。但是很多 Java 的基础类库,包括一些被广泛使用的高性能开发库都是基于 Unsafe 类开发,比如: Netty、Hadoop、Kafka等。

  • Unsafe 的主要作用:是在实质上扩展 Java 语言表达能力、便于在更高层(Java 层)代码里实现原本要在更底层(C 层)实现的核心库功能。
  • Unsafe 的主要功能:裸内存的申请/释放/访问,爹那个硬件的 atomic/volatile 支持,创建未初始化对象等。

3.Unsafe 常用的两个方法

/**
 * 读取对象o的第offset字节偏移量的一个long型数据
 */
public native long getLong(Object o, long offset);
/**
 * 将x写入对象o的第offset个字节的偏移量中
 */
public native long putLong(Object o , long offset, long x);

这类似于 C 的操作方法,带来了极大的性能提升,更重要的是,由于它避开了字段名,直接使用偏移量,就可以轻松绕过成员的可见性限制了

4.获取成员变量在对象中的偏移量

性能问题解决了,下一个问题是,怎么才能知道 threadLoalRandomSeed 成员变量在 Thread 中的偏移位置呢,这就需要用 Unsafe 的 objectFieldOffset() 方法了,请看下面的代码:

public static final sun.mis.Unsafe UNSAFE;
public static final long SEED;
public static final long PROBE;
public static final long SECONDARY;
static {
    try {
        UNSAFE = sun.misc.Unsafe.getUnsafe();
        Class<?> tk = Thread.class;
        // 获得 threadLocalRandomSeed在对象中的偏移量
        SEED = UNSAFE.objectFieldOffset(tk.getDelaredField("threadLocalRandomSeed"));
        // 获得 threadLocalRandomProbe在对象中的偏移量
        PROBE = UNSAFE.objectFieldOffset(tk.getDeclaredField("threadLocalRandomProbe"));
        // 获得 threadLocalRandomSecondarySeed 在对象中的偏移量
        SECONDARY = UNSAFE.objectFieldOffset(tk.getDeclaredField("threadLocalRandomSecondarySeed"));
    } catch (Exception e) {
        throw new Error(e);
    }
}

上面这段 static 代码,在 ThreadLocalRandom 类初始化的时候,就取得了 Thread 成员变量 threadLocalRandomSeed、threadLocalRandomProbe、threadLocalRandomSecondarySeed 在对象中偏移的位置。

因此,只要 ThreadLocalRandom 需要使用这些变量,都可以通过 Unsafe 的 getLong() 和 putLong() 来进行访问。(也可能是 getInt() 和 putInt())

5.ThreadLocalRandom.nextInt()源码分析

比如,在使用 ThreadLocalRandom.nextInt() 生成一个随机数的时候:

/**
 * @param bound 伪随机数上限
 * @return 获取[0, bound)的伪随机整数
 */
public int nextInt(int bound) {
    //bound范围校验
    if (bound <= 0)
        throw new IllegalArgumentException(BadBound);
    //根据当前线程中的种子计算新种子
    int r = mix32(nextSeed());
    //根据新种子和bound计算随机数
    int m = bound - 1;
    if ((bound & m) == 0) // power of two
        r &= m;
    else { // reject over-represented candidates
        for (int u = r >>> 1;
             u + m - (r = u % bound) < 0;
             u = mix32(nextSeed()) >>> 1)
            ;
    }
    return r;
}

/**
 * 用于根据当前种子,计算和更新下一个种子
 *
 * @return
 */
final long nextSeed() {
    Thread t;
    long r; // read and update per-thread seed
    //更新计算出的种子,即更新当前线程的threadLocalRandomSeed变量
    UNSAFE.putLong(t = Thread.currentThread(), SEED,
            //计算新种子,为原种子+增量
            r = UNSAFE.getLong(t, SEED) + GAMMA);
    return r;
}

/**
 * 种子增量
 */
private static final long GAMMA = 0x9e3779b97f4a7c15L;

6.反射和Unsafe性能测试

具体 Unsafe 的成员变量调用能有多快呢,让我们一起做个实验看下:

这里,我们自己写一个 ThreadTest 类,使用 反射 和 Unsafe 两种方法,来不停读写 threadLocalRandomSeed 成员变量,比较它们的性能差异。

6.1 代码实现:

import sun.misc.Unsafe;

import java.lang.reflect.Field;
import java.security.AccessController;
import java.security.PrivilegedExceptionAction;

/**
 * <p> @Title UnsafeTest
 * <p> @Description Unsafe性能测试
 *
 * @author ACGkaka
 * @date 2023/2/26 19:20
 */
public class UnsafeTest {
    static Field fThreadLoalRandomSeed;
    static final sun.misc.Unsafe UNSAFE;
    private static final long SEED;
    static {
        try {
            // 反射,取得 threadLocalRandomSeed的filed值
            fThreadLoalRandomSeed = Thread.class.getDeclaredField("threadLocalRandomSeed");
            fThreadLoalRandomSeed.setAccessible(true);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        try {
            // 获得Unsafe对象
            final PrivilegedExceptionAction<Unsafe> action = new PrivilegedExceptionAction<Unsafe>() {
                @Override
                public Unsafe run() throws Exception {
                    Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
                    theUnsafe.setAccessible(true);
                    return (Unsafe) theUnsafe.get(null);
                }
            };
            UNSAFE = AccessController.doPrivileged(action);
        } catch (Exception e) {
            throw new RuntimeException("Unable to load safe", e);
        }
        Class<?> tk = Thread.class;
        try {
            // 获得threadLocalRandomSeed在Thread对象中的偏移量
            SEED = UNSAFE.objectFieldOffset(tk.getDeclaredField("threadLocalRandomSeed"));
        } catch (NoSuchFieldException e) {
            throw new Error(e);
        }
    }

    // 使用反射的方法,读写threadLocalRandomSeed成员
    public static void byReflection() throws IllegalAccessException {
        long begin = System.currentTimeMillis();
        Thread t = Thread.currentThread();
        fThreadLoalRandomSeed.set(t, 0);
        for (int i = 0; i < 100_000_000; i++) {
            fThreadLoalRandomSeed.set(t, (long)fThreadLoalRandomSeed.get(t) + 1);
        }
        long end = System.currentTimeMillis();
        System.out.println("byRefletion spend: " + (end - begin) + "ms");
    }

    // 使用 Unsafe,读写 threadLocalRandommSeed成员
    public static void byUnsafe() throws IllegalAccessException {
        UNSAFE.putLong(Thread.currentThread(), SEED, 0);
        long begin = System.currentTimeMillis();
        Thread t = Thread.currentThread();
        for (int i = 0; i < 100_000_000; i++) {
            UNSAFE.putLong(t, SEED, UNSAFE.getLong(t, SEED) + 1);
        }
        long end = System.currentTimeMillis();
        System.out.println("byUnsafe spend: " + (end - begin) + "ms");
    }

    // 主函数,开始测试
    public static void main(String[] args) throws IllegalAccessException {
        for (int i = 0; i < 3; i++) {
            byReflection();
            byUnsafe();
        }
        System.out.println("===== 上面是预热,看下面的结果 =====");
        byReflection();
        byUnsafe();
    }
}

上述代码中,分别使用反射方式 byReflection() 和 Unsafe 的方式 byUnsafe() 来读写 threadLocalRandomSeed 变量 1 亿次,得到的测试结果如下:

6.2 执行结果:

byRefletion spend: 439ms
byUnsafe spend: 11ms
byRefletion spend: 875ms
byUnsafe spend: 11ms
byRefletion spend: 667ms
byUnsafe spend: 1ms
===== 上面是预热,看下面的结果 =====
byRefletion spend: 599ms
byUnsafe spend: 2ms

不难看出,使用 Unsafe 的方法远远优于反射的方法,这也是 JDK 内部,大量使用 Unsafe 来替代反射的原因。

整理完毕,完结撒花~





参考地址:

1.并发情况下,你还在用Random生成随机数?,https://baijiahao.baidu.com/s?id=1696431520418432282&wfr=spider&for=pc
2.Unsafe类详解,https://blog.csdn.net/WenWu_Both/article/details/123272282

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不愿放下技术的小赵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值