这将不是说明随机数生成器毕竟不是那么随机的文章之一。 因此,您中的那些人希望获得有关如何破解老虎机,继续前进的指南,在这里什么也看不到。
相反,它是有关一个不太常见的锁争用问题的帖子,该问题隐藏在Java API的随机数生成器中。
要打开该主题,让我们开始研究如何在java.util.Random类中处理并发。 java.util.Random的实例是线程安全的。 但是,在线程之间并发使用同一java.util.Random实例是同步的,并且我们已经发现趋向于触发影响应用程序性能的争用问题。
在您的日常企业应用程序中,这听起来似乎不是一个重要的问题–毕竟,您实际上有多少次实际执行了故意无法预测的事情? 相反,您只是在按照可预见的方式遵循业务规则。 我必须承认,尽管在某些情况下,这些业务规则比真正的随机种子生成算法所涉及的熵甚至更大,但这完全是另一回事。
但是,魔鬼隐藏在细节中,在这种情况下,碰巧是java.util.Random的子类,即java.util.SecureRandom 。 此类,如名称所述,应在随机数生成器的结果必须是加密安全的情况下使用。 由于人类未知的原因,在通常不希望随机性的密码安全方面具有重要意义的情况下,该实现已被选择为许多常见API的主干。
我们通过密切关注锁争用检测解决方案的采用来亲身体验这个问题。 根据结果,通过看上去无害的java.io.File.createTempFile()调用触发了Java应用程序中最常见的锁定问题之一。 在后台,这种临时文件的创建依赖于SecureRandom类来计算文件的名称。
private static final SecureRandom random = new SecureRandom();
static File generateFile(String prefix, String suffix, File dir) {long n = random.nextLong();if (n == Long.MIN_VALUE) {n = 0; // corner case} else {n = Math.abs(n);}return new File(dir, prefix + Long.toString(n) + suffix);
}
然后,在调用nextLong时,SecureRandom最终调用其方法nextBytes() ,该方法定义为synced :
synchronized public void nextBytes(byte[] bytes) {secureRandomSpi.engineNextBytes(bytes);
}
有人会说,如果我在每个线程中创建新的SecureRandom,我将不会遇到任何问题。 不幸的是,这并不是那么简单。 SecureRandom使用java.security.SecureRandomSpi的实现,无论如何最终都会争夺它(您可能会在Jenkins问题跟踪器中看到以下带有一些基准的bug讨论)
这与某些应用程序使用模式结合在一起(尤其是如果您有许多SSL连接依靠SecureRandom来实现其加密握手魔术),则有形成长期持久争用问题的趋势。
如果您可以控制源代码,则解决此问题的方法很简单–只需重建解决方案即可依靠java.util.ThreadLocalRandom进行多线程设计。 如果您坚持使用标准API,则解决方案可能会更复杂,并且需要大量重构。
故事的道德启示? 并发很难。 尤其是在您的系统构建块没有考虑到这一点时。 无论如何,我确实希望这篇文章至少从两个新库的诞生中拯救世界,在新库中,随机数生成器将成为竞争点。
翻译自: https://www.javacodegeeks.com/2015/03/shooting-yourself-in-the-foot-with-random-number-generators.html