赞
踩
最近在写一些业务代码时遇到一个需要产生随机数的场景,这时自然想到 jdk 包里的 Random
类。但出于对性能的极致追求,就考虑使用 ThreadLocalRandom
类进行优化,在查看 ThreadLocalRandom 实现的过程中,又追了下 Unsafe
有部分代码,整个流程下来,学到了不少东西,也通过搜索和提问解决了很多疑惑,于是总结成本文。
使用 Random 类时,为了避免重复创建的开销,我们一般将实例化好的 Random 对象设置为我们所使用服务对象的属性或静态属性,这在线程竞争不激烈的情况下没有问题,但在一个高并发的 web 服务内,使用同一个 Random 对象可能会导致线程阻塞。
Random 的随机原理是对一个”随机种子”进行固定的算术和位运算,得到随机结果,再使用这个结果作为下一次随机的种子。在解决线程安全问题时,Random 使用 CAS 更新下一次随机的种子 ,失败的线程则需要自旋重试 。可以想到,如果多个线程同时使用这个对象,就肯定会有一些线程执行 CAS 连续失败,进而导致线程阻塞。
jdk 的开发者自然考虑到了这个问题,在 concurrent 包内添加了 ThreadLocalRandom
类,第一次看到这个类名,我以为它是通过 ThreadLocal 实现的,进而想到恐怖的内存泄漏问题,但点进源码却没有 ThreadLocal 的影子,而是存在着大量 Unsafe 相关的代码。
我们来看一下它的核心代码:
UNSAFE.putLong(t = Thread.currentThread(), SEED, r = UNSAFE.getLong(t, SEED) + GAMMA);
- 1
翻译成更直观的 Java 代码就像:
Thread t = Thread.currentThread();
long r = UNSAFE.getLong(t, SEED) + GAMMA;
UNSAFE.putLong(t, SEED, r);
看上去非常眼熟,像我们平常往 Map 里 get/set 一样,以 Thread.currentThread()
获取到的当前对象里 key,以 SEED 随机种子作为 value。
但是以对象作为 key 是可能会造成内存泄漏的啊,由于 Thread 对象可能会大量创建,在回收时不 remove Map 里的 value 时会导致 Map 越来越大,最后内存溢出。
示例代码 :
import java.util.concurrent.ThreadLocalRandom; public class ThreadLocalRandomDemo { public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Player().start(); } } private static class Player extends Thread { @Override public void run() { System.out.println(getName() + ": " + ThreadLocalRandom.current().nextInt(100)); } } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。