Random原理:
在JDK7之前包括现在java.util.Random应该是使用比较广泛的随机数生成工具类,另外java.lang.Math中的随机数生成也是使用的java.util.Random的实例。下面先看看java.util.Random的使用:
public class RandomTest {
public static void main(String[] args) {
//(1)创建一个默认种子的随机数生成器
Random random = new Random();
//(2)输出10个在0-5(包含0,不包含5)之间的随机数
for (int i = 0; i < 10; ++i) {
System.out.println(random.nextInt(5));
}
}
}
Random的核心方法就是利用AtomicLong+CAS(乐观锁)更新种子数,更新种子数的公式是先(oldseed * multiplier + addend) & mask,然后再进行移位算法>>>
每个Random实例里面有一个原子性的种子变量用来记录当前的种子的值,当要生成新的随机数时候要根据当前种子计算新的种子并更新回原子变量。多线程下使用单个Random实例生成随机数时候,多个线程同时计算随机数计算新的种子时候多个线程会竞争同一个原子变量的更新操作,由于原子变量的更新是CAS操作,同时只有一个线程会成功,所以会造成大量线程进行自旋重试,这是会降低并发性能的,所以Random其实是线程安全的。当然在高并发下构造多个Random实例可能导致大量线程多次CAS重试。所以ThreadLocalRandom应运而生。
ThreadLocalRandom原理:
为了解决多线程高并发下Random的缺陷,JUC包下新增了ThreadLocalRandom类,首先,对于ThreadLocalRandom的实现,JDK7和JDK8的实现方式是完全不一样的,不过它们的思想都是一样的,都借助了ThreadLocal的原理,不同的是JDK7直接使用了ThreadLocal来实现,而JDK8只是借助这种思想,并没有直接借助ThreadLocal来实现下面首先看下它如何使用:
public class RandomTest {
public static void main(String[] args) {
//(10)获取一个随机数生成器
ThreadLocalRandom random = ThreadLocalRandom.current();
//(11)输出10个在0-5(包含0,不包含5)之间的随机数
for (int i = 0; i < 10; ++i) {
System.out.println(random.nextInt(5));
}
}
}
ThreadLocalRandom同样继承了Random类;
ThreadLocalRandom同样重写了setSeed和next(int bits)方法,另外对产生随机数的方法nextInt、nextLong等方法也都进行了重新,因为他们的核心代码由nextSeed方法实现。
ThreadLocalRandom的实例instance是static修饰的,所以对于多个线程,实例其实只有一个,而不像JDK7那样有多个。
增加了8个缓存行填充字段解决“伪共享”的问题,使每个线程的ThreadLocalRandom实例位于不同的缓存行,避免了竞争。
JDK8中ThreadLocalRandom的实现方法发生了变化,但是中心思想没有变,它不再使用ThreadLocalRandom实例本身维护线程各自的种子变量,而是将种子变量转移到Thread类中
ThreadLocalRandom使用ThreadLocal的原理,让每个线程内持有一个本地的种子变量,该种子变量只有在使用随机数时候才会被初始化,多线程下计算新种子时候是根据自己线程内维护的种子变量进行更新,从而避免了竞争。
总结:
由于具体的种子是存放到线程里面的,所以ThreadLocalRandom的实例里面只是与线程无关的通用算法,所以是线程安全的。
特别需要注意的就是一定不要在多线程之间共享同一个ThreadLocalRandom实例
,例如下面这种错误的用法:
Threadlocalrandom的错误用法代码 收藏代码
public class RandomTest implements Runnable {
public static void main(String[] args) {
ExecutorService taskPool = Executors.newCachedThreadPool();
for (int n = 1; n < 10; n++) {
taskPool.submit(new RandomTest());
}
taskPool.shutdown();
}
private static Random random = ThreadLocalRandom.current();//不要在多线程之间共享同一个实例
@Override
public void run() {
System.out.println(random.nextInt());//错误的使用方式
}
}
以上的使用方式是错误的,这种错误的使用方式将可能导致多线程产生相同的随机数。
正确的threadlocalrandom使用方式代码
@Override
public void run() {
System.out.println(ThreadLocalRandom.current().nextInt());//每一次产生随机数时都要执行ThreadLocalRandom.current()
}
因为多线程情况下,ThreadLocalRandom.current()方法会帮我们找到各自线程内部维护的种子,从而产生各自的随机数,所以在每一次想要产生随机数的时候都要先执行ThreadLocalRandom.current()方法。