目录
一、ThreadLocalRandom(并发编程之美)
1. Random类及其局限性
2. ThreadLocalRandom产生原因
3. ThreadLocalRandom
二、ThreadLocalRandom(自我整理)
1. Random
2. ThreadLocalRandom
3. UUID
一、ThreadLocalRandom(并发编程之美)
ThreadLocalRandom类是JDK 7在JUC包下新增的随机数生成器,它弥补了Random类在多线程下的缺陷。
1. Random类及其局限性
在JDK 7之前包括现在,java.util.Random都是使用比较广泛的随机数生成工具类,而且java.lang.Math中的随
机数生成也使用的是java.util.Random的实例。
下面先看看java. util.Random的使用方法:
public static void main(String[] args) {// (1) 创建一个默认种子的随机数生成器Random random = new Random();// (2) 输入5个在[0,10)之间的随机数for (int i = 0; i < 5; i++) {System.out.println(random.nextInt(5));}}
代码(1)创建一个默认随机数生成器,并使用默认的种子。
代码(2)输出10个在0~5(包含0,不包含5)之间的随机数。
随机数的生成需要一个默认的种子,这个种子其实是一个long类型的数字,你可以在创建Random对象时通过
构造函数指定,如果不指定,则在默认构造函数内部生成一个默认的值。有了默认的种子后,如何生成随机数
呢?
由此可见,新的随机数的生成需要两个步骤:
- 首先根据老的种子生成新的种子。
- 然后根据新的种子来计算新的随机数。
步骤(4)要保证原子性,也就是说当多个线程根据同一个老种子计算新种子时,
第一个线程的新种子被计算出来后,第二个线程要丢弃自己老的种子,而使用第一个线程的新种子来计算自己
的新种子,依此类推,只有保证了这个,才能保证在多线程下产生的随机数是随机的。
Random函数使用一个原子变量达到了这个效果,在创建Random对象时初始化的种子就被保存到了种子原子
变量里面,下面看next()的代码:
- 代码(6)获取当前原子变量种子的值。
- 代码(7)根据当前种子值计算新的种子。
- 代码(8)使用CAS操作,它使用新的种子去更新老的种子,在多线程下可能多个线程都同时执行到了代码(6),那么可能多个线程拿到的当前种子的值是同一个,然后执行步骤(7)计算的新种子也都是一样的,但是步骤(8)的CAS操作会保证只有一个线程可以更新老的种子为新的,失败的线程会通过循环重新获取更新后的种子作为当前种子去计算老的种子,这就解决了上面提到的问题,保证了随机数的随机性。
- 代码(9)使用固定算法根据新的种子计算随机数。
2. ThreadLocalRandom产生原因
为了弥补多线程高并发情况下 Random 的缺陷,在 JUC 包下新增了ThreadLocalRandom类。
如果每个线程都维护一个种子变量,则每个线程生成随机数时都根据自己老的种子计算新的种子,并使用新种
子更新老的种子,再根据新种子计算随机数,就不会存在竞争问题了,这会大大提高并发性能。
ThreadLocalRandom 原理如图:
每个Random实例里面都有一个原子性的种子变量用来记录当前的种子值,当要生成新的随机数时需要根据当
前种子计算新的种子并更新回原子变量。在多线程下使用单个Random实例生成随机数时,当多个线程同时计
算随机数来计算新的种子时,多个线程会竞争同一个原子变量的更新操作,由于原子变量的更新是CAS操作,
同时只有一个线程会成功,所以会造成大量线程进行自旋重试,这会降低并发性能,所以
ThreadLocalRandom应运而生。
3. ThreadLocalRandom
为了弥补多线程高并发情况下Random的缺陷,在JUC包下新增了ThreadLocalRandom类。
下面首先看下如何使用它:
public static void main(String[] args) {// (1) 创建一个默认种子的随机数生成器ThreadLocalRandom random = ThreadLocalRandom.current();// (2) 输入5个在[0,10)之间的随机数for (int i = 0; i < 5; i++) {System.out.println(random.nextInt(5));}}
其中,代码(1)调用ThreadLocalRandom.current()来获取当前线程的随机数生成器。
下面来分析下ThreadLocalRandom的实现原理:
ThreadLocalRandom使用ThreadLocal的原理,让每个线程都持有一个本地的种子变量,该种子变量只有在
使用随机数时才会被初始化。
在多线程下计算新种子时是根据自己线程内维护的种子变量进行更新,从而避免了竞争。
二、ThreadLocalRandom(自我整理)
1. Random
Random:产生一个伪随机数(通过相同的种子,产生的随机数是相同的);
Random r=new Random();System.out.println(r.nextBoolean());System.out.print(r.nextInt(50));//随机生成0~50的随机数,不包括50System.out.println(r.nextInt(20)+30);//随机生成30~50的随机数,不包括50
2. ThreadLocalRandom
ThreadLocalRandom:是JDK 7之后提供并发产生随机数,能够解决多个线程发生的竞争争夺。
ThreadLocalRandom不是直接用new实例化,而是第一次使用其静态方法current()。
从Math.random()改变到ThreadLocalRandom有如下好处:
我们不再有从多个线程访问同一个随机数生成器实例的争夺。
取代以前每个随机变量实例化一个随机数生成器实例,我们可以每个线程实例化一个。
ThreadLocalRandom t=ThreadLocalRandom.current();System.out.println(t.nextInt(50));//随机生成0~50的随机数,不包括50System.out.println(t.nextInt(30, 50));//随机生成30~50的随机数,不包括50
3. UUID
UUID: UUID含义是通用唯一识别码 (Universally Unique Identifier),这 是一个软件建构的标准,也是被开
源软件基金会 (Open Software Foundation, OSF) 的组织在分布式计算环境 (Distributed Computing
Environment, DCE) 领域的一部份。UUID 的目的,是让分布式系统中的所有元素,都能有唯一的辨识资讯,
而不需要透过中央控制端来做辨识资讯的指定。如此一来,每个人都可以建立不与其它人冲突的 UUID。在这
样的情况下,就不需考虑数据库建立时的名称重复问题。目前最广泛应用的 UUID,即是微软的 Microsoft's
Globally Unique Identifiers (GUIDs),而其他重要的应用,则有 Linux ext2/ext3 档案系统、LUKS 加密分
割区、GNOME、KDE、Mac OS X 等等。
String u=UUID.randomUUID().toString();
System.out.println(u);
生成5个字符的验证码:
String content="ABCDEFGHIJKLMNOPQRSTUVWHYZ";//创建23个大写字母的字符串content+=content.toLowerCase();//把大写字母转换成小写字母,相连接content+="0123456789";//连接0~9的数字。Random r=new Random();//创建一个随机数对象StringBuilder b=new StringBuilder(5);//创建空间大小为5的StringBuilder对象for (int i = 0; i <5; i++) {char n=content.charAt(r.nextInt(content.length()));//截取一个从0到content.length()之间的字符,循环输出5个不同的字符,追加到一起b.append(n);
}
System.out.println(b.toString());//转成字符串输出5个字符