1 Random
Random用来创建伪随机数。所谓伪随机数,是指只要给定一个初始的种子,产生的随机数序列是完全一样的。要生成一个随机数,可以使用nextInt()、nextLong()、nextFloat()、nextDouble():
Random r = new Random();
r.nextInt(); // 2071575453,每次都不一样
r.nextInt(10); // 5,生成一个[0,10)之间的int
r.nextLong(); // 8811649292570369305,每次都不一样
r.nextFloat(); // 0.54335...生成一个[0,1)之间的float
r.nextDouble(); // 0.3716...生成一个[0,1)之间的double
1.1 Random的安全性
如果我们在创建Random实例时指定一个种子,就会得到完全确定的随机数序列。
public static void main(String[] args) {
Random r = new Random(12345);
for (int i = 0; i < 10; i++) {
System.out.println(r.nextInt(100));
}
// 51, 80, 41, 28, 55...
}
注意:
- 当种子特定的时候,上面程序不管执行多少次,结果都一样。
1.2 Random的并发性
Random在多线程的环境是并发安全的,它解决竞争的方式是使用用原子类,本质上上也就是CAS + Volatile保证线程安全。
public
class Random implements java.io.Serializable {
/**
* The internal state associated with this pseudorandom number generator.
* (The specs for the methods in this class describe the ongoing
* computation of this value.)
*/
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)
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));
}
}
在Random类中,有一个AtomicLong的域,用来保存随机种子。其中每次生成随机数时都会根据随机种子做移位操作以得到随机数。然后使用CAS将其更新,再返回种子的移位后值。这里不断的循环CAS操作种子,直到成功。
注意:
- 并发处理使用原子类AtomicLong在大量竞争时,由于很多CAS操作会造成失败,不断的Spin,而造成CPU开销比较大而且吞吐量也会下降。
2 SecureRandom
实际上真正的真随机数只能通过量子力学原理来获取,而我们想要的是一个不可预测的安全的随机数,SecureRandom就是用来创建安全的随机数的:
public static void main(String[] args) {
SecureRandom sr = null;
try {
sr = SecureRandom.getInstanceStrong(); // 获取高强度安全随机数生成器
} catch (NoSuchAlgorithmException e) {
sr = new SecureRandom(); // 获取普通的安全随机数生成器
}
byte[] buffer = new byte[16];
sr.nextBytes(buffer); // 用安全随机数填充buffer
System.out.println(Arrays.toString(buffer));
}
SecureRandom无法指定种子,它使用RNG(random number generator)算法。JDK的SecureRandom实际上有多种不同的底层实现,有的使用安全随机种子加上伪随机数算法来产生安全的随机数,有的使用真正的随机数生成器。实际使用的时候,可以优先获取高强度的安全随机数生成器,如果没有提供,再使用普通等级的安全随机数生成器。
3 ThreadLocalRandom
ThreadLocalRandom是Random的子类,它是将Seed随机种子隔离到当前线程的随机数生成器,从而解决了Random在Seed上竞争的问题,它的处理思想和ThreadLocal本质相同。
public class ThreadLocalRandom extends Random {
private static final AtomicInteger probeGenerator =
new AtomicInteger();
/**
* The seed increment
*/
private static final long GAMMA = 0x9e3779b97f4a7c15L;
/**
* The next seed for default constructors.
*/
private static final AtomicLong seeder = new AtomicLong(initialSeed());
/** Rarely-used holder for the second of a pair of Gaussians */
private static final ThreadLocal<Double> nextLocalGaussian =
new ThreadLocal<Double>();
static final ThreadLocalRandom instance = new ThreadLocalRandom();
//在生成随机数时调用,为每个线程设置种子
final long nextSeed() {
Thread t; long r; //
UNSAFE.putLong(t = Thread.currentThread(), SEED,
r = UNSAFE.getLong(t, SEED) + GAMMA);
return r;
}
// 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);
}
}
使用 r = UNSAFE.getLong(t, SEED)获取当前线程中threadLocalRandomSeed变量的值,然后在种子的基础上累加GAMMA值作为新种子,然后使用UNSAFE的putLong方法把新种子放入当前线程的threadLocalRandomSeed变量。
总结:
- ThreadLocalRandom使用ThreadLocal的原理,让每个线程内持有一个本地的种子变量,该种子变量只有在使用随机数时候才会被初始化,多线程下计算新种子时候是根据自己线程内维护的种子变量进行更新,从而避免了竞争。
真伪随机数 ——Random和SecureRandom - yonyong - 博客园Random和ThreadLocalRandom - 怀瑾握瑜XI - 博客园
网友评论