Using Random Number Generator with Current Time vs Without
我想了解使用一个以System.currentTimeMillis()作为种子的随机数生成器和只使用默认构造函数之间的区别。也就是说,这两者的区别是什么:
还有这个:
号
我知道这些数字是伪随机的,但是我还没有完全理解细节,以及它们是如何在"随机性"级别之间发生的,当当前时间被用作种子时,以及当使用默认构造函数时。
- 你能告诉我们在问这个问题之前你查阅过哪些文章、指南或教程吗?这可能有助于我们了解你到底不明白什么。
- stackoverflow.com/questions/20060725/ja‌&8203;va中的默认种子prng
提供自己的种子对于有意多次生成相同伪随机值序列的模拟非常有用。不过,一般来说,使用默认的构造函数也是一样的。
当使用默认构造函数时,文档会说:
This constructor sets the seed of the random number generator to a value very likely to be distinct from any other invocation of this constructor.
号
换句话说,它在内部产生自己的种子。细节取决于所使用的特定Java实现。我看到的一个实现是这样的:
1 2 3 4 5 6
| private static volatile long seedBase = 0;
public Random() {
setSeed (System. nanoTime() + seedBase );
++seedBase ;
} |
随机性的实际质量并没有改变。如果您关心随机序列的质量,也可以使用java.security.SecureRandom,它具有更好的加密行为。(请参见,例如,此线程。)
- 我很好奇,您在哪里看到这个实现?除了在易失性字段上误用++,这比JDK7或JDK8使用的方法要简单得多。
- @yshavit-这是Android的api 22库中使用的一个。
- @Yshavit JDK 6同样简单。
- @yshavit-我不认为易变字段上的竞争条件是一个严重的问题;即使两个线程相互覆盖,System.nanoTime() + seedBase在随后的调用中保持相同的可能性也很小,而且在任何情况下,合同只保证"很可能是不同的"。我同意你的看法,这是一个糟糕的实现;使用AtomicLong会更好。
- @Tedhop我完全同意,这根本不是一个严重的问题——尽管我不同意相似种子的可能性:如果两个线程之间的距离足够近以至于它们相互覆盖,那么它们将拥有相同的nanoTime(),这是很有可能的,特别是考虑到大多数平台实际上没有给你纳秒的精度(我的Mac似乎是only"提供微秒精度)。但是,像你一样,我本以为原子龙是更好的方法——如果没有别的东西的话,建立好习惯!
如果希望运行之间的随机序列相同,可以指定种子。通常不希望发生这种情况,所以每次运行和系统都使用不同的种子。currentTimeMillis()是常用的合理种子。如果您面对多个线程,在多个线程同时初始化RNG的情况下,您可能希望避免使用Surviv.TrimeTimeMILISH(),并让Java使用它自己的初始化。
- 通常,您总是希望让Java使用它自己的初始化。它比System.CurrentTimeMillis()更好,所以为什么要降级到这个级别?
- @安德烈亚斯,谢谢!你澄清了我对这个评论的困惑!我一直认为使用System.currentTimeMillis()有点多余。我说得对吗?.事实上,在我使用随机类的程序中,我惊讶地发现当我使用System.currentTimeMillis()时,性能更差。我不知道是什么原因。我基本上只是困惑,但我想,好吧,让我们用一个有效的!顺便说一句,我不知道我是否应该把这个答案标记为正确的,因为正是这个评论才真正回答了我的问题!
- @安德烈亚斯,如果你能根据你在上面的评论给出一个更详细的答案,我会非常感激,所以我可以接受。谢谢!
- @Ayepete System.CurrentTimeMillis()显然是多余的。我不知道你所说的"性能下降"是什么意思。您正在初始化多个RNG吗?您可能希望使用单例模式,或者只为每个线程保留一个RNG。
- @Josep Valls我基本上是研究用启发式方法来解决组合优化问题的,所以在我所指的案例中,得到的解更接近于最优解,并且使用默认的构造函数比使用System.currentTimeMillis更频繁。据我所知,我在代码的不同点初始化了多个RNG,所以我不知道这是否对生成的值有任何影响。是否间接建议在整个项目中使用单个RNG?我用了一根线。
- @Ayepete甚至JDK6中更简单的实现也在使用System.nanoTime(),其种子不断变化,从而减少了多线程冲突。System.currentTimeMillis()是对内置实现的降级。故事的寓意:不要指定种子,除非你想重复使用种子进行多次运行。
如果您研究Random的默认构造函数的实现,可以看到它在内部使用System.nanoTime()。此外,它使用种子"uniquifier"使随后的种子更加独特。然而,这需要访问static final AtomicLong。因此,如果您有一个高度并发的应用程序,其中许多线程正在构造Random实例,那么最好不要使用默认的构造函数来避免在种子生成上的争用。如果要确保两个线程永远不能获得相同的种子,则应采用默认的构造函数。这也就是说,在实践中,99%的情况下,你采用哪种变体是不相关的。
当TED HOPP正确地声明时,默认构造函数的行为依赖于具体的JDK实现,并且在Java版本之间也有所不同。
另请参阅并发使用java.util.random中的争用,了解与Random类的另一个争用问题。
- 种子如何在内部生成的细节随着Java实现而变化。在Java 7和8中,"超级用户"是一个7位的超级用户,它避免了您所描述的多线程问题。
- 这取决于用例。如果您想避免在任何情况下为不同的线程使用相同的种子,那么您是对的。但是,如果您希望避免争用,并且能够与相同的种子共存,那么原子长就是一个问题。
- EDOCX1 0是否是一个问题,我的观点是简单地说,"如果你查看EDCOX1的默认构造函数1",你会发现不同的东西取决于你使用的是什么版本的Java。在某些实现中,"uniquifier"只是一个volatile字段。不存在线程争用,但存在争用条件(并且不保证唯一性)。
- 当然,有不同的实现是正确的。不过,如果使用Java 7或8,则是相关的。另请参见stackoverflow.com/questions/223313552/…