javarandom线程安全_Java并发包中ThreadLocalRandom类ThreadLocalRandom类是JDK7在JUC包下新增的随机数⽣成器,它弥补了Random类在多线程下的缺陷。
⼀、Random类及其局限性
java.util.Random常⽤来⽣成随机数,随机数的⽣成需要⼀个默认的种⼦,这个种⼦其实是⼀个long类型的数字,通常在创建Random对象时,通过构造函数指定,如果不指定的则会在默认构造函数内部⽣成⼀个默认的值。
⽣成随机数通常需要以下两个步骤:
1. ⾸先根据⽼的种⼦⽣成新的种⼦。
2. 然后根据新的种⼦来计算新的随机数。
⽐如Random的nextInt⽅法,源码如下:public int nextInt(int bound) {
//参数检查
if (bound <= 0)
java生成随机数的方法throw new IllegalArgumentException(BadBound);
//根据⽼的种⼦⽣成新的种⼦
int r = next(31);
//根据新的种⼦计算随机数
int m = bound - 1;
if ((bound & m) == 0) // i.e., bound is a power of 2
r = (int)((bound * (long)r) >> 31);
else {
for (int u = r;
u - (r = u % bound) + m
u = next(31))
;
}
return r;
}
在单线程环境下:每次调⽤nextInt()⽅法都是根据⽼的种⼦来计算新的种⼦,并⽣成随机数,可以保证随机数产⽣的随机性的。
在多线程环境下:多个线程可能都拿同⼀个⽼的种⼦来⽣成新的种⼦,这时就会导致多个线程⽣成的同⼀个新种⼦,进⽽导致多线程⽣成的随机数相同,这并不是我们想要的。
Random⽣成随机数不具有原⼦性,只有保证其原⼦性,才能保证在多线程下产⽣的随机数是随机的。
Random函数使⽤⼀个原⼦变量达到了这个效果,在创建Random对象时初始化的种⼦就被保存到了种⼦原⼦变量中,⽐如Random的next()⽅法,其源码如下:protected int next(int bits) {
long oldseed, nextseed;
AtomicLong seed = this.seed;
do {
//获取当前原⼦变量中的值
oldseed = ();
//根据当前种⼦值计算新的种⼦
nextseed = (oldseed * multiplier + addend) & mask;
} while (!seedpareAndSet(oldseed, nextseed)); //使⽤CAS操作,它使⽤新的种⼦去更新⽼的种⼦,它保证在多线程环境下只有⼀个线程可以更新⽼的种⼦为新的,失败的线程会通过循环重新获取更新后的种⼦作为当前种⼦去计算⽼的种⼦,这就可以保证随机数的随机性。
return (int)(nextseed >>> (48 - bits));
}
每个Random实例⾥⾯都有⼀个原⼦性的种⼦变量⽤来记录当前的种⼦值,当要⽣成新的随机数时需
要根据当前种⼦计算新的种⼦并更新回原⼦变量。
在多线程环境下使⽤单个Random实例⽣成随机数时,当多个线程同时计算随机数来计算新的种⼦时,多个线程会竞争同⼀个原⼦变量的更新操作,由于原⼦变量的更新是CAS操作,同时只有⼀个线程会成功,所以会造成⼤量线程进⾏⾃旋重试,这会降低并发性能,为了解决这个问题引⼊了ThreadLocalRandom类
Random的缺点是多个线程会使⽤同⼀个原⼦性种⼦变量,从⽽导致对原⼦变量更新的竞争,⽽降低并发性能。
⼆、ThreadLocalRandom
获取⼀个随机数⽣成器:ThreadLocalRandom random = ThreadLocalRandom.current();
current()⽅法⽤来获取ThreadLocalRandom实例,并初始化调⽤线程中的threadLocalRandomSeed和threadLocalRandomProbe变量。
ThreadLocalRandom的实现原理与ThreadLocal的实现原理相同。
ThreadLocal通过让每⼀个线程复制⼀份变量,使得在每个线程对变量进⾏操作时实际是操作⾃⼰本地内存⾥⾯的副本,从⽽避免了对共享变量进⾏同步。
使⽤ThreadLocalRandom使得每个线程都维护⾃⼰的⼀个种⼦变量,每个线程在⽣成随机数时都根据⾃⼰⽼的种⼦计算新的种⼦,并使⽤新的种⼦更新⽼的种⼦,再根据新种⼦⽣成随机数,就不会存在竞争问题了,从⽽提⾼了并发性能。
三、ThreadLocalRandom的源码解析
从ThreadLocalRandom的源码可以看出,ThreadLocalRandom继承了Random类,并重写了它的nextInt()⽅法,在ThreadLocalRandom类中并没有使⽤继承⾃Random类的原⼦性种⼦变量。
在ThreadLocalRandom中并没有存放具体的种⼦,具体的种⼦存放在具体的调⽤线程的threadLocalRandomSeed变量中。
当线程调⽤ThreadLocalRandom的current⽅法时,ThreadLocalRandom负责初始化调⽤线程的threadLocalRandomSeed变量,也就是初始化种⼦。
当调⽤ThreadLocalRandom的nextInt()⽅法时,实际上是获取当前线程的threadLocalRandomSeed变量作为当前种⼦来计算新的种⼦,然后更新新的种⼦到当前线程的threadLocalRandomSeed变量中,
⽽后再根据新种⼦并使⽤具体算法计算随机数。
threadLocalRandomSeed变量就是Thread类⾥⾯的⼀个普通long变量,它并不是原⼦变量。
ThreadLocalRandom中有两个原⼦性变量,定义如下:
1. private static final AtomicInteger probeGenerator = new AtomicInteger();
2. private static final AtomicLong seeder = new AtomicLong(initialSeed());
在初始化调⽤线程的种⼦和探针变量时会使⽤到它们,每个线程只会使⽤⼀次。关于ThreadLocalRandom来⽣成随机数的其他类型也是同理。不再多说。
⽰例代码:import urrent.ThreadLocalRandom;
/**
* @ClassName: ThreadLocalRamdomTest
* @Description: 使⽤ThreadLocalRandom类⽣成随机数
* @Author: liuhefei
**/
public class ThreadLocalRamdomTest {
public static void main(String[] args) {
//1. 获取⼀个随机数⽣成器
ThreadLocalRandom random = ThreadLocalRandom.current();
//2. 随机⽣成0到5之间的随机数(包含0, 不包含5)
for(int i = 0; i
System.out.Int(5));
System.out.Double(10));
}
}
}
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论