干掉Random:这个类才是获取随机数的王者!
前言
ThreadLocalRandom
UNSAFE.putLong(t = Thread.currentThread(), SEED, r = UNSAFE.getLong(t, SEED) + GAMMA);
Thread t = Thread.currentThread();
long r = UNSAFE.getLong(t, SEED) + GAMMA;
UNSAFE.putLong(t, SEED, r);
功能
不过再仔细看 ThreadLocalRandom 类的核心代码,发现并不是简单的 Map 操作,它的 getLong() 方法需要传入两个参数,而 putLong() 方法需要三个参数,查看源码发现它们都是 native 方法,我们看不到具体的实现。两个方法签名分别是:
public native long getLong(Object var1, long var2);
public native void putLong(Object var1, long var2, long var4);
虽然看不到具体实现,但我们可以查得到它们的功能,下面是两个方法的功能
介绍:
putLong(object, offset, value) 可以将 object 对象内存地址偏移 offset 后的位置后四个字节设置为 value。
getLong(object, offset) 会从 object 对象内存地址偏移 offset 后的位置读取四个字节作为 long 型返回。
那么这两个方法”不安全”在哪呢?
它们的不安全并不是在这两个方法执行期间报错,而是未经保护地改变内存,会引起别的方法在使用这一段内存时报错。
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
// Unsafe 设置了构造方法私有,getUnsafe 获取实例方法包私有,在包外只能通过反射获取
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
// Test 类是一个随手写的测试类,只有一个 String 类型的测试类
Test test = new Test();
test.ttt = "12345";
unsafe.putLong(test, 12L, 2333L);
System.out.println(test.value);
}
不过 Unsafe 的其他方法可不一定像这一对方法一样,使用他们时可能需要注意另外的安全问题,之后有遇到再说。
那么 ThreadLocalRandom 是不是安全的呢,再回过头来看一下它的实现。
ThreadLocalRandom 的实现需要 Thread 对象的配合,在 Thread 对象内存在着一个属性 threadLocalRandomSeed,它保存着这个线程专属的随机种子,而这个属性在 Thread 对象的 offset,是在 ThreadLocalRandom 类加载时就确定了的,具体方法是 SEED = UNSAFE.objectFieldOffset(Thread.class.getDeclaredField("threadLocalRandomSeed"));
我们知道一个对象所占用的内存大小在类被加载后就确定了的,所以使用 Unsafe.objectFieldOffset(class, fieldName) 可以获取到某个属性在类中偏移量,而在找对了偏移量,又能确定数据类型时,使用 ThreadLocalRandom 就是很安全的。
在查找这些问题的过程中,我也产生了两个疑问点。
使用场景
内存布局
在写代码时还是要多注意查看依赖库的具体实现,不然可能踩到意想不到的坑,而且多看看并没有坏处,仔细研究一下还能学到更多。
感谢您的阅读,也欢迎您发表关于这篇文章的任何建议,关注我,技术不迷茫!小编到你上高速。
正文结束
1.心态崩了!税前2万4,到手1万4,年终奖扣税方式1月1日起施行~