JUC源码分析-其它工具类(一)ThreadLocalRandom
JUC源码分析-其它工具类(一)ThreadLocalRandom
ThreadLocalRandom 是 JDK7 在 JUC 包下新增的随机数生成器,它解决了 Random 在多线程下多个线程竞争内部唯一的原子性种子变量而导致大量线程自旋重试的不足。需要注意的是 Random 本身是线程安全的。同时 Random 实例不是安全可靠的加密,可以使用 java.security.SecureRandom 来提供一个可靠的加密。
1. 随机数算法介绍
常用的随机数算法有两种:同余法(Congruential method)和梅森旋转算法(Mersenne twister)。Random 类中用的就是同余法中的一种 - 线性同余法(见Donald Kunth《计算机编程的艺术》第二卷,章节3.2.1)。
在程序中为了使表达式的结果小于某个值,常常采用取余的操作,结果是同一个除数的余数,这种方法叫同余法(Congruential method)。
线性同余法是一个很古老的随机数生成算法,它的数学形式如下:
Xn+1 = (a * Xn + c) % m
其中,m > 0, 0 < a < m, 0 < c < m
2. Random 源码分析
JDK 中的 Random 类生成的是伪随机数,使用的是 48-bit 的种子,然后调用线性同余方程,代码很简洁。
2.1 数据结构
private static final long multiplier = 0x5DEECE66DL; // 相当于上面表达式中的 a
private static final long mask = (1L << 48) - 1; // 相当于上面表达式中的 m
private static final long addend = 0xBL; // 相当于上面表达式中的 c
// seed 生成的随机数种子
private final AtomicLong seed;
2.2 构造函数
// ^ 让 seed 更加随机
public Random() {
this(seedUniquifier() ^ System.nanoTime());
}
public Random(long seed) {
if (getClass() == Random.class)
// initialScramble 初始化的随机数
this.seed = new AtomicLong(initialScramble(seed));
else {
this.seed = new AtomicLong(); // 子类重写 setSeed
setSeed(seed);
}
}
// 不太明白,不过也不影响代码阅读
private static final AtomicLong seedUniquifier = new AtomicLong(8682522807148012L);
private static long seedUniquifier() {
for (;;) {
long current = seedUniquifier.get();
long next = current * 181783497276652981L;
if (seedUniquifier.compareAndSet(current, next))
return next;
}
}
// 初始化的随机数
private static long initialScramble(long seed) {
return (seed ^ multiplier) & mask;
}
构造函数初始化了随机数种子 seed,之后的随机数都是在这个基础上进行计算的。 如果传入的 seed 值一样,那么生成的随机数也就是一样的了。
@Test
public void test() {
long seed = 343L;
Random random1 = new Random(seed);
Random random2 = new Random(seed);
Assert.assertEquals(random1.nextInt(), random2.nextInt());
Assert.assertEquals(random1.nextInt(), random2.nextInt());
Assert.assertEquals(random1.nextInt(), random2.nextInt());
}
2.3 生成随机数
public int nextInt() {
return next(32);
}
public int nextInt(int bound) {
if (bound <= 0)
throw new IllegalArgumentException(BadBound);
// 1. 生成随机数
int r = next(31);
int m = bound - 1;
// 2. 生成的随机数不能超过 bound。 (n&-n)==n 也可以判断2^n
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 < 0; u = next(31))
;
}
return r;
}
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));
}
可以看到上面代码可知新的随机数的生成需要两个步骤:
(1) 首先需要根据老的种子生成新的种子。
(2) 然后根据新的种子来计算新的随机数。
3. ThreadLocalRandom 源码分析
为了解决多线程高并发下 Random 的缺陷,JUC 包下新增了 ThreadLocalRandom 类。更多参考并发包中ThreadLocalRandom类原理剖析
3.1 ThreadLocalRandom 原理
@Test
public void testThreadLocalRandom() {
ThreadLocalRandom random = ThreadLocalRandom.current();
System.out.println(random.nextInt());
}
从名字看会让我们联想到基础篇讲解的 ThreadLocal,ThreadLocal 的出现就是为了解决多线程访问一个变量时候需要进行同步的问题,让每一个线程拷贝一份变量,每个线程对变量进行操作时候实际是操作自己本地内存里面的拷贝,从而避免了对共享变量进行同步。
实际上 ThreadLocalRandom 的实现也是这个原理,Random 的缺点是多个线程会使用同一个原子性种子变量,会导致对原子变量更新的竞争。那么如果每个线程维护自己的一个种子变量,每个线程生成随机数时候根据自己老的种子计算新的种子,并使用新种子更新老的种子,然后根据新种子计算随机数,就不会存在竞争问题。这会大大提高并发性能,如下图 ThreadLocalRandom 原理:

3.2 数据结构

从 ThreadLocalRandom 类图中可以看到和 Random 保存一份 seed 不同,ThreadLocalRandom 的种子变量保存在 Thread.threadLocalRandomSeed 变量中,通过 Unsafe 操作这个变量。关于 threadLocalRandomSeed、threadLocalRandomProbe、threadLocalRandomSecondarySeed 这三个变量,Thread 类有相关的注释:
/** The current seed for a ThreadLocalRandom */
// 1. 和 Random 中的 seed 类似
long threadLocalRandomSeed;
/** Probe hash value; nonzero if threadLocalRandomSeed initialized */
// 2. 在 CurrentHashMap 中有使用。probe 是探测的意思,
int threadLocalRandomProbe;
/** Secondary seed isolated from public ThreadLocalRandom sequence */
int threadLocalRandomSecondarySeed;
需要注意的是这三个值都不能为 0,因为 0 在 ThreadLocalRandom 中有特殊的含义,表示还未初始化,调用 localInit() 进行初始化。
3.3 构造函数
boolean initialized;
private ThreadLocalRandom() {
initialized = true; // false during super() call
}
public static ThreadLocalRandom current() {
if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
localInit();
return instance;
}
ThreadLocalRandom 构造函数为私有的,只能通过 current 方法构建,如果 PROBE 还是默认值 0 表示未初始化,调用 localInit 进行初始化。
3.4 生成随机数 nextInt
// Random 一样也有两步:一是根据老的种子生成新的种子;
// 二是根据新的种子来计算新的随机数
public int nextInt() {
return mix32(nextSeed());
}
public int nextInt(int bound) {
if (bound <= 0)
throw new IllegalArgumentException(BadBound);
int r = mix32(nextSeed());
// 1. 生成随机数
int m = bound - 1;
// 2. 生成的随机数不能超过 bound
// 2.1 bound 是 z^n 则直接取余
if ((bound & m) == 0) // power of two
r &= m;
// 2.2 没看明白,但肯定是取 [0, bound] 之间的数
else { // reject over-represented candidates
for (int u = r >>> 1; u + m - (r = u % bound) < 0; u = mix32(nextSeed()) >>> 1)
;
}
return r;
}
ThreadLocalRandom 和 Random 一样也有两步:
(1) 根据老的种子生成新的种子;
(2) 根据新的种子来计算新的随机数。
nextInt(int bound) 和 nextInt 的思路是一样的,先调用 mix32(nextSeed()) 函数生成随机数(int类型的范围),再对参数 n 进行判断,如果 n 恰好为 2 的方幂,那么直接移位就可以得到想要的结果;如果不是 2 的方幂,那么就关于 n 取余,最终使结果在[0,n)范围内。另外,for 循环语句的目的应该是防止结果为负数。
当bound为2n2n时, bound与生成的随机数相乘, 相当于取随机数的前log2boundlog2bound
其它情况时, 将int的取值范围231−1231−1以bound为区间范围划分为n组, 最后一个区间的数不够bound个, 如果生成的随机数是从这个区间内生成的, 则难以保证随机性, 故需要重新生成.
// 生成新的种子,保存在 Thread.threadLocalRandomSeed 中。 GAMMA=0x9e3779b97f4a7c15L
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;
}
// 根据新种子生成随机数,随机数算法和 Random 一样了
private static int mix32(long z) {
z = (z ^ (z >>> 33)) * 0xff51afd7ed558ccdL;
return (int)(((z ^ (z >>> 33)) * 0xc4ceb9fe1a85ec53L) >>> 32);
}
3.5 其它方法
(1) getProbe
getProbe 用法参考 ConcurrentHashMap#fullAddCount 方法。
static final int getProbe() {
return UNSAFE.getInt(Thread.currentThread(), PROBE);
}
static final int advanceProbe(int probe) {
probe ^= probe << 13; // 异或位运算。 xorshift
probe ^= probe >>> 17;
probe ^= probe << 5;
UNSAFE.putInt(Thread.currentThread(), PROBE, probe);
return probe;
}
(2) nextSecondarySeed
static final int nextSecondarySeed() {
int r;
Thread t = Thread.currentThread();
if ((r = UNSAFE.getInt(t, SECONDARY)) != 0) {
r ^= r << 13; // xorshift
r ^= r >>> 17;
r ^= r << 5;
}
else {
localInit();
if ((r = (int)UNSAFE.getLong(t, SEED)) == 0)
r = 1; // avoid zero
}
UNSAFE.putInt(t, SECONDARY, r);
return r;
}
参考:
- 并发包中ThreadLocalRandom类原理剖析
- 《ThreadLocalRandom和Random性能测试》:http://www.importnew.com/12460.html
- 《Java中的random函数是如何实现的》:https://my.oschina.net/hosee/blog/600392
- 《解密随机数生成器》:https://blog.csdn.net/lihui126/article/details/46236109
每天用心记录一点点。内容也许不重要,但习惯很重要!
JUC源码分析-其它工具类(一)ThreadLocalRandom的更多相关文章
- JUC源码分析-集合篇:并发类容器介绍
JUC源码分析-集合篇:并发类容器介绍 同步类容器是 线程安全 的,如 Vector.HashTable 等容器的同步功能都是由 Collections.synchronizedMap 等工厂方法去创 ...
- JUC源码分析-线程池篇(二)FutureTask
JUC源码分析-线程池篇(二)FutureTask JDK5 之后提供了 Callable 和 Future 接口,通过它们就可以在任务执行完毕之后得到任务的执行结果.本文从源代码角度分析下具体的实现 ...
- JUC源码分析-线程池篇(一):ThreadPoolExecutor
JUC源码分析-线程池篇(一):ThreadPoolExecutor Java 中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池.在开发过程中,合理地使用线程池 ...
- JUC源码分析-集合篇(十)LinkedTransferQueue
JUC源码分析-集合篇(十)LinkedTransferQueue LinkedTransferQueue(LTQ) 相比 BlockingQueue 更进一步,生产者会一直阻塞直到所添加到队列的元素 ...
- JUC源码分析-集合篇(八)DelayQueue
JUC源码分析-集合篇(八)DelayQueue DelayQueue 是一个支持延时获取元素的无界阻塞队列.队列使用 PriorityQueue 来实现. 队列中的元素必须实现 Delayed 接口 ...
- 鸿蒙内核源码分析(构建工具篇) | 顺瓜摸藤调试鸿蒙构建过程 | 百篇博客分析OpenHarmony源码 | v59.01
百篇博客系列篇.本篇为: v59.xx 鸿蒙内核源码分析(构建工具篇) | 顺瓜摸藤调试鸿蒙构建过程 | 51.c.h.o 编译构建相关篇为: v50.xx 鸿蒙内核源码分析(编译环境篇) | 编译鸿 ...
- JUC源码分析-线程池篇(三)ScheduledThreadPoolExecutor
JUC源码分析-线程池篇(三)ScheduledThreadPoolExecutor ScheduledThreadPoolExecutor 继承自 ThreadPoolExecutor.它主要用来在 ...
- JUC源码分析-线程池篇(三)Timer
JUC源码分析-线程池篇(三)Timer Timer 是 java.util 包提供的一个定时任务调度器,在主线程之外起一个单独的线程执行指定的计划任务,可以指定执行一次或者反复执行多次. 1. Ti ...
- JUC源码分析-集合篇(九)SynchronousQueue
JUC源码分析-集合篇(九)SynchronousQueue SynchronousQueue 是一个同步阻塞队列,它的每个插入操作都要等待其他线程相应的移除操作,反之亦然.SynchronousQu ...
随机推荐
- Dubbo入门到精通学习笔记(二十):MyCat在MySQL主从复制的基础上实现读写分离、MyCat 集群部署(HAProxy + MyCat)、MyCat 高可用负载均衡集群Keepalived
文章目录 MyCat在MySQL主从复制的基础上实现读写分离 一.环境 二.依赖课程 三.MyCat 介绍 ( MyCat 官网:http://mycat.org.cn/ ) 四.MyCat 的安装 ...
- 【C++第一个Demo】---控制台RPG游戏2【通用宏、背包类】
[通用 ]--一些游戏中常用的宏.函数和枚举 #ifndef _MARCO_H_ #define _MARCO_H_ //------------------------常用系统库---------- ...
- 力扣算法——141LinkedListCycel【E】
Given a linked list, determine if it has a cycle in it. To represent a cycle in the given linked lis ...
- cmd 运行 java 文件
在安装好jdk 并配置好环境变量的情况下 原因一:没有指定class文件的路径 例如HI是变异好的class文件,并且在d:/RJAZB里面 如果写成 Java HI 则会报错 正确做法 java ...
- 1005 -- I Think I Need a Houseboat
I Think I Need a Houseboat Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 105186 Acc ...
- LeetCode Array Easy 53. Maximum Subarray 个人解法 和分治思想的学习
Description Given an integer array nums, find the contiguous subarray (containing at least one numbe ...
- Mysql中(@i:=@i+1)的作用
Oracle中有一个伪列rownum,可以在生成查询结果表的时候生成一组递增的序列号.MySQL中没有这个伪列,但是有时候要用,可以用如下方法模拟生成一列自增序号. (1)sql示例:select ( ...
- MySQL索引与事务
MySQL索引与事务 链接:https://pan.baidu.com/s/1ANGg3Kd_28BzQrA5ya17fQ 提取码:ekpy 复制这段内容后打开百度网盘手机App,操作更方便哦 1.索 ...
- visual studio 自定义警告标签
写代码中经常会遇见某段代码写的不大合适或者是有隐患,但是一时半会有没时间不去完善,总体上不影响程序,也就放下了 结果时间一久就给忘了 vs提供了自定义警告的功能,这样就能有个提醒啦,方便以后改进 us ...
- Gabor滤波器的理解
搬以前写的博客[2014-02-28 20:03] 关于Gabor滤波器是如何提取出特征点,这个过程真是煎熬.看各种文章,结合百度.文章内部的分析才有一点点明白. Gabor滤波器究竟是什么? 很 ...