共计 4183 个字符,预计需要花费 11 分钟才能阅读完成。
目录
一:简述
二:Random 的性能差在哪里
三:ThreadLocalRandom 的简略应用
四:为什么 ThreadLocalRandom 能在保障线程平安的状况下还能有不错的性能
一:简述
如果咱们想要生成一个随机数,通常会应用 Random 类。然而在并发状况下 Random 生成随机数的性能并不是很现实,明天给大家介绍一下 JUC 包中的用于生成随机数的类 –ThreadLocalRandom.(本文基于 JDK1.8)
二:Random 的性能差在哪里
Random 随机数生成是和种子 seed 无关,而为了保障线程安全性,Random 通过 CAS 机制来保障线程安全性。从 next() 办法中咱们能够发现 seed 是通过自旋锁和 CAS 来进行批改值的。如果在高并发的场景下,那么可能会导致 CAS 一直失败,从而导致一直自旋,这样就可能会导致服务器 CPU 过高。
protected int next(int bits) {
long oldseed, nextseed;
AtomicLong seed = this.seed;
do {oldseed = seed.get();
nextseed = (oldseed * multiplier + addend) & mask;
} while (!seed.compareAndSet(oldseed, nextseed));
return (int)(nextseed >>> (48 - bits));
}
三:ThreadLocalRandom 的简略应用
应用的办法很简略,通过 ThreadLocalRandom.current()获取到 ThreadLocalRandom 实例,而后通过 nextInt(),nextLong()等办法获取一个随机数。
代码:
@Test
void test() throws InterruptedException {new Thread(()->{ThreadLocalRandom random = ThreadLocalRandom.current();
System.out.println(random.nextInt(100));
}).start();
new Thread(()->{ThreadLocalRandom random = ThreadLocalRandom.current();
System.out.println(random.nextInt(100));
}).start();
Thread.sleep(100);
}
运行后果:
四:为什么 ThreadLocalRandom 能在保障线程平安的状况下还能有不错的性能
咱们能够看一下 ThreadLocalRandom 的代码实现。
首先咱们很容易看出这是一个饿汉式的单例
/** Constructor used only for static singleton */
private ThreadLocalRandom() {initialized = true; // false during super() call
}
/** The common ThreadLocalRandom */
static final ThreadLocalRandom instance = new ThreadLocalRandom();
咱们能够看到 PROBE 成员变量代表的是 Thread 类的 threadLocalRandomProbe 属性的内存偏移量,SEED 成员变量代表的是 Thread 类的 threadLocalRandomSeed 属性的内存偏移量,SECONDARY 成员变量代表的是 Thread 类的 threadLocalRandomSecondarySeed 属性的内存偏移量。
// Unsafe mechanics
private static final sun.misc.Unsafe UNSAFE;
private static final long SEED;
private static final long PROBE;
private static final long SECONDARY;
static {
try {UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> tk = Thread.class;
SEED = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSeed"));
PROBE = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomProbe"));
SECONDARY = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSecondarySeed"));
} catch (Exception e) {throw new Error(e);
}
}
能够看到 Thread 类中的确有这三个属性
Thread 类:
@sun.misc.Contended("tlr")
// 以后 Thread 的随机种子 默认值是 0
long threadLocalRandomSeed;
/** Probe hash value; nonzero if threadLocalRandomSeed initialized */
@sun.misc.Contended("tlr")
// 用来标记以后 Thread 的 threadLocalRandomSeed 是否进行了初始化 0 代表没有,非 0 代表曾经初始化 默认值是 0
int threadLocalRandomProbe;
/** Secondary seed isolated from public ThreadLocalRandom sequence */
@sun.misc.Contended("tlr")
// 以后 Thread 的二级随机种子 默认值是 0
int threadLocalRandomSecondarySeed;
接下来咱们看 ThreadLocalRandom.current()办法。
ThreadLocalRandom.current()
ThreadLocalRandom.current()的作用次要是初始化随机种子,并且返回 ThreadLocalRandom 的实例。
首先通过 UNSAFE 类获取以后线程的 Thread 对象的 threadLocalRandomProbe 属性,看随机种子是否曾经初始化。没有初始化,那么调用 localInit()办法进行初始化
public static ThreadLocalRandom current() {
// 获取以后线程的
if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
localInit();
return instance;
}
localInit()
localInit()办法的作用就是初始化随机种子,能够看到代码很简略,就是通过 UNSAFE 类对以后 Thread 的 threadLocalRandomProbe 属性和 threadLocalRandomSeed 属性进行一个赋值。
static final void localInit() {int p = probeGenerator.addAndGet(PROBE_INCREMENT);
int probe = (p == 0) ? 1 : p; // skip 0
long seed = mix64(seeder.getAndAdd(SEEDER_INCREMENT));
Thread t = Thread.currentThread();
UNSAFE.putLong(t, SEED, seed);
UNSAFE.putInt(t, PROBE, probe);
}
接下来以 nextInt()办法为例,看 ThreadLocalRandom 是如何生成到随机数的。咱们能够看出随机数正是通过 nextSeed()办法获取到随机种子,而后通过随机种子而生成。所以重点看 nextSeed()办法是如何获取到随机种子的。
public int nextInt(int bound) {if (bound <= 0)
throw new IllegalArgumentException(BadBound);
int r = mix32(nextSeed());
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;
}
nextSeed()
nextSeed()办法的作用是获取随机种子,代码很简略,就是通过 UNSAFE 类获取以后线程的 threadLocalRandomSeed 属性,并且将原来的 threadLocalRandomSeed 加上 GAMMA 设置成新的 threadLocalRandomSeed。
final long nextSeed() {
Thread t; long r; // read and update per-thread seed
UNSAFE.putLong(t = Thread.currentThread(), SEED,
r = UNSAFE.getLong(t, SEED) + GAMMA);
return r;
}
小结:
ThreadLocalRandom 为什么线程平安?是因为它将随机种子保留在以后 Thread 对象的 threadLocalRandomSeed 变量中,这样每个线程都有本人的随机种子,实现了线程级别的隔离,所以 ThreadLocalRandom 也并不需要像 Random 通过自旋锁和 cas 来保障随机种子的线程安全性。在高并发的场景下,效率也会绝对较高。
注: 各位有没有发现 ThreadLocalRandom 保障线程平安的形式和 ThreadLocal 有点像呢
须要留神的点:
1.ThreadLocalRandom 是单例的。
2. 咱们每个线程在获取随机数之前都须要调用一下 ThreadLocalRandom.current()来初始化以后线程的随机种子。
3. 了解 ThreadLocalRandom 须要对 UnSafe 类有所理解,它是 Java 提供的一个能够间接通过内存对变量进行获取和批改的一个工具类。java 的 CAS 也是通过这个工具类来实现的。