java并发编程之美 笔记
ThreadLocalRandom
是在jdk7之后在JUC
包下新增的随机数生成器,补充了Random
在多线程下的缺陷。在学习它之前,我们先学习下Random
类。
Random类
创建APIs
public class Random implements java.io.Serializable {
private final AtomicLong seed;
private static final long multiplier = 0x5DEECE66DL;
private static final long addend = 0xBL;
private static final long mask = (1L << 48) - 1;
private static final double DOUBLE_UNIT = 0x1.0p-53; // 1.0 / (1L << 53)
//1.使用seed参数的 构造函数
public Random() {
//1.1可以理解为 seed = f(seedUniquifier(),System.nanoTime()) ;通过“异或”,计算出seed
//1.2 计算出seed后,调用this(seed);
this(seedUniquifier() ^ System.nanoTime());
}
//2.给定sedd参数,构造函数
public Random(long seed) {
if (getClass() == Random.class)
//2.1 initialScramble(seed): 可以理解为 y = f(seed);
//2.2 创建AtomicLong,并将初始值设置为 f(seed);
this.seed = new AtomicLong(initialScramble(seed));
else {
this.seed = new AtomicLong();
//调用setSeed(), 该方法可被sup-CLASS 覆盖重写;
//默认的实现方法同上面if分支内逻辑.
setSeed(seed);
}
}
//2.1 将seed做位运算: y = f(seed); 拼凑新的seed
private static long initialScramble(long seed) {
return (seed ^ multiplier) & mask;
}
//setSeed()可由子类覆盖重写;
synchronized public void setSeed(long seed) {
this.seed.set(initialScramble(seed));
haveNextNextGaussian = false;
}
}
上述代码中,在使用无参的构造函数new Random()
时,会使用seedUniquifier() ^ System.nanoTime()
来计算出long seed
,这里单独将seedUniquifier()
拿出来分析下:
- seedUniquifier()
private static final AtomicLong seedUniquifier = new AtomicLong(8682522807148012L);
private static long seedUniquifier() {
for (;;) {
long current = seedUniquifier.get();
//计算出新的seed,
long next = current * 181783497276652981L;
//循环尝试cas操作,更新seedUniquifier,当操作成功时,返回next;
if (seedUniquifier.compareAndSet(current, next))
return next;
}
}
获取随机数
public class Random implements java.io.Serializable {
//bound: 边界值,最大值;
public int nextInt(int bound) {
if (bound <= 0)
throw new IllegalArgumentException(BadBound);
//获取seed种子
int r = next(31);
int m = bound - 1;
if ((bound & m) == 0)
//当bound为2的冥次方时,bound & m = bound & (bound -1) == 0
//计算出随机数r,然后返回
r = (int)((bound * (long)r) >> 31);
else {
//计算出随机数r
for (int u = r;
u - (r = u % bound) + m < 0;
u = next(31))
;
}
return r;
}
protected int next(int bits) {
long oldseed, nextseed;
AtomicLong seed = this.seed;
do {
//获取旧的seed
oldseed = seed.get();
//根据旧的seed计算出新的seed,可以理解为:nextseed = f(oldseed);
nextseed = (oldseed * multiplier + addend) & mask;
//当多个线程并发执行时,不断执行CAS操作尝试,直至成功.
} while (!seed.compareAndSet(oldseed, nextseed));
//重新按位运算计算出最终的seed : 可以理解为 nextseed = f(nextseed)
return (int)(nextseed >>> (48 - bits));
}
}
- 出现的问题
当多个线程同时计算随机数时,会同时执行seed.compareAndSet(oldseed, nextseed)
竞争seed原子变量的更新操作,由于更新时CAS操作,它是线程安全的操作,此时只有一个线程会成功,所以会导致大量的线程在进行自旋尝试
,这降低了并发性能,如下图:
为了解决这个问题,TheadLocalRandom
应运而生。
TheadLocalRandom
TheadLocalRandom继承自Random类.
初始化
public class ThreadLocalRandom extends Random {
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;
//记录threadLocalRandomSeed ,threadLocalRandomProbe,threadLocalRandomSecondarySeed 在各个thread实例中内存偏移量
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);
}
}
boolean initialized;
private ThreadLocalRandom() {
initialized = true;
}
static final ThreadLocalRandom instance = new ThreadLocalRandom();
public static ThregetIntadLocalRandom current() {
//当前线程的 PROBE对应的threadLocalRandomProbe为0时,需要进行初始化
if (UNSAFE.(Thread.currentThread(), PROBE) == 0)
//给当前线程初始化,
localInit();
return instance;
}
初始化
private static final int PROBE_INCREMENT = 0x9e3779b9;
private static final long SEEDER_INCREMENT = 0xbb67ae8584caa73bL;
private static final AtomicInteger probeGenerator = new AtomicInteger();
private static final AtomicLong seeder = new AtomicLong(initialSeed());
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();
//给线程t的 threadLocalRandomSeed ,threadLocalRandomProbe设置值..
UNSAFE.putLong(t, SEED, seed);
UNSAFE.putInt(t, PROBE, probe);
}
}
Thread相关属性
public class Thread implements Runnable {
/**当前线程ThreadLocalRandom 的 seed */
@sun.misc.Contended("tlr")
long threadLocalRandomSeed;
/** 当threadLocalRandomSeed初始化后, threadLocalRandomProbe值不为0*/
@sun.misc.Contended("tlr")
int threadLocalRandomProbe;
/** Secondary seed isolated from public ThreadLocalRandom sequence */
@sun.misc.Contended("tlr")
int threadLocalRandomSecondarySeed;
}
nextInt()
public int nextInt(int bound) {
if (bound <= 0)
throw new IllegalArgumentException(BadBound);
//获取seed
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;
}
private static final long GAMMA = 0x9e3779b97f4a7c15L;
//获取线程t当前的seed,并更新
final long nextSeed() {
Thread t; long r;
//SEED增加GAMMA,作为新种子
UNSAFE.putLong(t = Thread.currentThread(), SEED,
r = UNSAFE.getLong(t, SEED) + GAMMA);
return r;
}
这里nextSeed()
函数获取种子解决了Random高并发获取随机数时CAS操作自旋等待影响性能的问题。
它的结局而方法简单来说,就是每个seed都保存在Thread的threadLocalRandomSeed
属性中,它是线程私有的,这就避免了高并发的资源竞争问题,可以使用下图来解释: