【Java】深入理解Java随机数

【Java】深入理解Java随机数本文介绍Java随机数的内容

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

随机数

根据密码学原理,随机数的随机性检验可以分为三个标准:

  • 统计学伪随机性。统计学伪随机性指的是在给定的随机比特流样本中,1的数量大致等于0的数量,同理,“10”“01”“00”“11”四者数量大致相等。类似的标准被称为统计学随机性。满足这类要求的数字在人类“一眼看上去”是随机的。
  • 密码学安全伪随机性。其定义为,给定随机样本的一部分和随机算法,不能有效的演算出随机样本的剩余部分。
  • 真随机性。其定义为随机样本不可重现。实际上只要给定边界条件,真随机数并不存在,可是如果产生一个真随机数样本的边界条件十分复杂且难以捕捉(比如计算机当地的本底辐射波动值),可以认为用这个方法演算出来了真随机数。

相应的,随机数也分为三类:

  • 伪随机数:满足第一个条件的随机数。
  • 密码学安全的伪随机数:同时满足前两个条件的随机数。可以通过密码学安全伪随机数生成器计算得出。
  • 真随机数:同时满足三个条件的随机数。

java.lang.Math.random()

定义:public static double random()

源码实现:

public static double random() { 
   
    return Math.RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble();
}
private static final class RandomNumberGeneratorHolder { 
   

    static final Random randomNumberGenerator = new Random();

    private RandomNumberGeneratorHolder() { 
   }
    
}

根据源码分析得:

java.lang.Math 类里有一个私有静态内部类,内有一个静态的 java.util.Random 类对象,调用其 nextDouble() 方法,生成 [0.0, 1.0) 范围内的伪随机浮点数。

注意:使用的时候别忘了强转int或者long,除非需要的是浮点数。

java.util.Random

主要API:

  • protected int next​(int bits):生成下一个伪随机数。(注意protected,直接调用不了的)
  • public boolean nextBoolean():从此随机数生成器的序列中返回下一个伪随机、均匀分布的布尔值。
  • public void nextBytes​(byte[] bytes):生成随机字节并将其放入用户提供的字节数组中。
  • public double nextDouble():返回下一个伪随机数,它是此随机数生成器的序列中介于0.0和1.0之间的均匀分布的double值。
  • public float nextFloat():返回下一个伪随机数,此随机数生成器的序列在0.0和1.0之间均匀分布的float值。
  • public double nextGaussian():返回下一个伪随机数,与该随机数生成器的序列的 μ = 0.0 μ=0.0 μ=0.0 σ 2 = 1.0 σ^2=1.0 σ2=1.0 的高斯(“正态”)分布双精度值。
  • public int nextInt():返回下一个伪随机数,它是此随机数生成器序列中均匀分布的int值。
  • public int nextInt​(int bound):返回一个伪随机数,它从此随机数生成器的序列中提取,在0()和指定值(不含)之间均匀分布的int值。
  • public long nextLong():返回下一个伪随机数,该随机数是从此随机数生成器的序列中均匀分布的long值。

通过new Random().nextInt(to-from)+from,输出两端的边界,就可以生成左闭右开区间的随机整数了。

还可以自己借助循环逐位生成的大随机数:

Random random = new Random();
StringBuilder result = new StringBuilder();
for (int i = 0; i < 9; i++) { 
   
    result.append(random.nextInt(10));
}
System.out.println(Long.parseLong(result.toString()));

Long.parseLong(result.toString())这步处理是为了消除先导0。

或者一步到位:

Random random = new Random();
StringBuilder result = new StringBuilder();
result.append(random.nextInt(9)+1);
for (int i = 0; i < 8; i++) { 
   
    result.append(random.nextInt(10));
}
System.out.println(Long.parseLong(result.toString()));

对于 java.util.Random,JVM 通过传入的种子(seed)来确定生成随机数的区间。

种子是一个数字,可称“种子值”,它为生成新的随机数提供了基础。
只要种子值相同,获取的随机数的序列就是一致的,而且生成的结果都是可以预测的。

这里seed对象的类型是 java.util.concurrent.AtomicLong,而不是简简单单的long。

种子值还是可变的。

public void setSeed​(long seed) 方法可生成随机数:

public synchronized void setSeed(long seed) { 
   
    this.seed.set(initialScramble(seed));
    this.haveNextNextGaussian = false;
}

调用的initialScramble():

private static long initialScramble(long seed) { 
   
    return (seed ^ 25214903917L) & 281474976710655L;
}

这是一种伪随机数的实现,而不是真正的随机数 。

伪随机只是统计学上的概念,生成的伪随机数是有一定规律的,而这个规律出现的周期随着伪随机算法的优劣而不同。一般来说这个“周期”比较长,但是也是可以预测的。

再看看next(int bits)方法的源码:

protected int next(int bits) { 
   
    AtomicLong seed = this.seed;
    long oldseed;
    long nextseed;
    do { 
   
        oldseed = seed.get();
        nextseed = oldseed * 25214903917L + 11L & 281474976710655L;
    } while(!seed.compareAndSet(oldseed, nextseed));
    return (int)(nextseed >>> 48 - bits);
}

Random 类是线程安全的,根据 next(int bits) 还有seed的属性,可以看出其内部使用 CAS 来保证线程安全性。

一般而言,CAS 相比加锁有一定的优势(参考“乐观锁”),但并不一定意味着高效
我们可以在每次使用 Random 时都去 new 一个新的线程私有化的 Random 对象。在不同线程上并发使用相同的Random实例可能会导致争用,从而导致性能不佳,问题源于使用种子来生成随机数。

首先,旧种子和新种子存储在两个辅助变量上。在这一点上,创造新种子的规则并不重要。

要保存新种子,使用 compareAndSet() 方法将旧种子替换为下一个新种子,但这仅仅在旧种子对应于当前设置的种子的条件下才会触发。

如果此时的值由并发线程操纵,则该方法返回false,这意味着旧值与例外值不匹配。因为是循环内进行的操作,那么会发生自旋,直到变量与例外值匹配。这可能会导致性能不佳和线程竞争。

使用 java.lang.ThreadLocal 来维护线程私有化对象 以及 使用java.util.concurrent.ThreadLocalRandom 都更适合于多线程并发环境。

java.util.concurrent.ThreadLocalRandom

ThreadLocalRandom是隔离到当前线程的随机数生成器。

像Math类使用的全局Random生成器一样,ThreadLocalRandom会使用内部生成的种子进行初始化,否则无法进行修改。

ThreadLocalRandom 继承了Random并添加选项以限制其使用到相应的线程实例。为此,ThreadLocalRandom的实例保存在相应线程的内部映射中,并通过调用current()来返回对应的Random。

如果适用的话,在并发程序中使用ThreadLocalRandom而不是共享Random对象通常会遇到更少的开销和竞争。

当多个任务(例如,每个ForkJoinTask)在线程池中并行使用随机数时,使用ThreadLocalRandom特别合适。

ThreadLocalRandom的构造器是private的:

private ThreadLocalRandom() { 
   }

获取对象要调用current():

public static ThreadLocalRandom current() { 
   
    if (U.getInt(Thread.currentThread(), PROBE) == 0) { 
   
        localInit();
    }

    return instance;
}

当所有用法都是这种形式时,永远不可能在多个线程之间意外地共享ThreadLocalRandom。

调用方法:

ThreadLocalRandom.current().nextX()

nextX()包括nextBoolean()、nextDouble()、nextFloat()、nextInt()、nextLong()等……

例如:

ThreadLocalRandom.current().nextInt()

ThreadLocalRandom的实例不是加密安全的,还是普通的“伪随机数”。考虑在对安全敏感的应用程序中使用SecureRandom。此外,除非系统属性java.util.secureRandomSeed设置为true,否则默认构造的实例不会使用加密的随机种子。

java.security.SecureRandom

通过对Random的一些分析我们可以知道Random事实上是伪随机,是可以推导出规律的,而且依赖种子(seed)。如果抽奖或者其他一些对随机数敏感的场景时,用Random不合适。JDK提供了 java.security.SecureRandom 来解决问题。

SecureRandom提供了加密功能强的随机数生成器(RNG)。

加密强度高的随机数至少要符合FIPS 140-2“加密模块的安全性要求”第4.9.1节中指定的统计随机数生成器测试。此外,SecureRandom必须产生不确定的输出。因此,传递给SecureRandom对象的任何种子材料都必须不可预测,并且所有SecureRandom输出序列必须具有加密强度,如RFC 4086:安全性的随机性要求中所述。

许多SecureRandom实现采用伪随机数生成器(PRNG,也称为确定性随机位生成器或DRBG)的形式,这意味着它们使用确定性算法从随机种子生成伪随机序列。其他实现可以产生真正的随机数,而其他实现则可以使用两种技术的组合。

SecureRandom是强随机数生成器,它可以产生高强度的随机数,产生高强度的随机数依赖两个重要的因素:种子算法。算法是可以有很多的,通常如何选择种子是非常关键的因素。
Random的种子是 System.currentTimeMillis(),所以它的随机数都是可预测的, 是弱伪随机数。

强伪随机数的生成思路:收集计算机的各种信息,键盘输入时间,内存使用状态,硬盘空闲空间,IO延时,进程数量,线程数量等信息,CPU时钟,来得到一个近似随机的种子,主要是达到不可预测性

说的更通俗就是,使用加密算法生成很长的一个随机种子,让人无法猜测出种子,也就无法推导出随机序列数。

调用者通过无参数构造函数或getInstance方法之一获取SecureRandom实例。

例如:

  • SecureRandom r1 = new SecureRandom();
  • SecureRandom r2 = SecureRandom.getInstance("NativePRNG");
  • SecureRandom r3 = SecureRandom.getInstance("DRBG", DrbgParameters.instantiation(128, RESEED_ONLY, null));

上面的第三条语句返回支持特定实例化参数的特定算法的SecureRandom对象。实现的有效实例化参数必须匹配此最小请求,但不一定相同。例如,即使请求不需要某个功能,实际的实例也可以提供该功能。一个实现可以延迟地实例化SecureRandom,直到它被实际使用为止,但是有效的实例化参数必须在创建后立即确定,并且getParameters() 始终应返回不变的相同结果。

SecureRandom的典型调用者调用以下方法来检索随机字节:

  • SecureRandom random = new SecureRandom();
  • byte[] bytes = new byte[20];
  • random.nextBytes(bytes);

调用者还可以调用generateSeed(int)方法来生成给定数量的种子字节(例如,为其他随机数生成器提供种子):byte[] seed = random.generateSeed(20);

不播种新创建的PRNG SecureRandom对象(除非它是由SecureRandom(byte [])创建的)。对nextBytes的首次调用将强制其从实现特定的熵源中播种自身。如果先前调用过setSeed,则不会发生这种自我播种。

通过调用reseed或setSeed方法,可以随时重新播种SecureRandom。重新设定种子的方法从其熵源读取熵输入以重新设定其自身的种子。 setSeed方法要求调用者提供种子。

请注意,并非所有SecureRandom实施都支持种子

一些SecureRandom实现可能在其nextBytes(byte [],SecureRandomParameters)reseed(SecureRandomParameters)方法中接受SecureRandomParameters参数,以进一步控制这些方法的行为。

注意:根据实现的不同,例如,在各种类Unix操作系统上,如果熵源是/dev/random,则在收集熵时,generateSeed、reseed、nextBytes方法可能会阻塞。

SecureRandom对象可安全用于多个并发线程。

通过在注册提供程序时将服务提供程序属性“ ThreadSafe”设置为“ true”,SecureRandom服务提供程序可以公告它是线程安全的。 否则,此类将改为同步对SecureRandomSpi实现的以下方法的访问:

  • SecureRandomSpi.engineSetSeed(byte[])
  • SecureRandomSpi.engineNextBytes(byte[])
  • SecureRandomSpi.engineNextBytes(byte[], SecureRandomParameters)
  • SecureRandomSpi.engineGenerateSeed(int)
  • SecureRandomSpi.engineReseed(SecureRandomParameters)

System.currentTimeMillis()

可以用System.currentTimeMillis()生成随机数:

利用System.currentTimeMillis(),获取从1970年1月1日0时0分0秒(这与UNIX系统有关,Java就这么搞的)到此刻的一个long型的毫秒数,取模之后即可得到所需范围内的随机数。

int max=100,min=1;
long randomNum = System.currentTimeMillis();  
int ran3 = (int) (randomNum%(max-min)+min);  
System.out.println(ran3);
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/158809.html原文链接:https://javaforall.net

(0)
上一篇 2022年7月26日 下午6:46
下一篇 2022年7月26日 下午6:46


相关推荐

  • html 简单表格代码「建议收藏」

    html 简单表格代码「建议收藏」&lt;!DOCTYPEhtml&gt;&lt;html&gt; &lt;head&gt; &lt;title&gt;&lt;/title&gt; &lt;/head&gt; &lt;body&gt; &lt;tablestyle="withd:600px"border="1"&gt; &lt;capti

    2022年8月11日
    11
  • 文心一言免费API Key获取与使用全攻略

    文心一言免费API Key获取与使用全攻略

    2026年3月12日
    5
  • 百度通用翻译api使用

    百度通用翻译api使用百度通用翻译 api 使用官方 api 文档 http api fanyi baidu com api trans product apidoc 第一步 注册百度账号 自行注册 第二步申请百度翻译 api 获得 appid 以及 securityKey 申请教程 https jingyan baidu com article 3f16e00305bb

    2026年3月17日
    2
  • JSP九大内置对象及其作用

    JSP九大内置对象及其作用JSP 九大对象 内置对象 又叫隐含对象 有 9 个内置对象 不需要预先声明就可以在脚本代码和表达式中随意使用 1 request 对象 javax servlet HttpServletR request 对象代表了客户端的请求信息 主要用于接受通过 HTTP 协议传送到服务器的数据 包括头信息 系统信息 请求方式以及请求参数等 request 对象的作用域为一次请求 2 r

    2026年3月16日
    2
  • 并发编程面试题(2020最新版)「建议收藏」

    文章目录基础知识并发编程的优缺点为什么要使用并发编程(并发编程的优点)并发编程有什么缺点并发编程三要素是什么?在Java程序中怎么保证多线程的运行安全?并行和并发有什么区别?什么是多线程,多线程的优劣?线程和进程区别什么是线程和进程?进程与线程的区别什么是上下文切换?守护线程和用户线程有什么区别呢?如何在Windows和Linux上查找哪个线程cpu利用率最高?什么是线程死锁形成死锁的…

    2022年4月18日
    42
  • Linux中查看cuda版本

    Linux中查看cuda版本安装torch时,需要查看cuda版本,可以用如下命令查看:nvcc-V

    2022年5月2日
    59

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号