从Java 8开始,有什么理由写“new Random()”吗?

2022-09-01 12:18:18

出于某种原因,我曾经认为这是线程不安全的,a-la或,并且实现为用块或.java.util.RandomHashMapBitSetMath.random()RandomsynchronizedThreadLocalRandom.current().nextDouble()

实际上,事实证明java.util.Random是线程安全的(通过原子)。因此,结论是:即使我需要在单个线程中进行一些随机输入,使用也是有意义的,因为内部没有原子读取和写入,编译为锁定指令并发出内存屏障。ThreadLocalRandom

此外,由于Java 8本质上是一个单例,因此其状态保存在类的某些字段中。因此,方法不是访问,而只是静态字段读取,即非常便宜。ThreadLocalRandomjava.lang.ThreadThreadLocalRandom.current()ThreadLocalMap

我有两个问题:

  1. 从计算机科学的角度来看,几个线性同余随机生成器(初始化方式为s)的输出是否与单个线性同余随机生成器(实例)的输出是相同的“随机”?ThreadLocalRandomjava.util.Random

  2. 如果第一个问题的答案是肯定的,那么是否有任何理由编写构造(没有种子)而不是,曾经?new Random()ThreadLocalRandom.current()

更新。我认为像这样的调用可能是不正确的,因为Thread的随机生成器状态可能没有在工作线程中初始化,但似乎覆盖了方法,并且,使上述构造正确。ThreadLocalRandom.current().ints().parallel().collect(...)ForkJoinPoolThreadLocalRandomints()longs()doubles()


答案 1

1...

这取决于实现,但对于Java来说,它不会那么糟糕,因为Java有一个静态的唯一种子原子长,每次创建随机时都会纵。然而,在其他语言或实现中,我不会感到惊讶,但事实并非如此,他们可能只使用系统时间(Java也使用系统时间,但组合使用唯一种子)。在某些系统上,您可以为多个线程获得相同的种子。

经过进一步的检查和一些实际的测试(尽管测试很脆弱),似乎我以前可能是错的,因为同时使用许多(我说的是100k)随机数生成器实际上更糟(即使它们是不同的实例)。我不完全确定它的种子冲突,还是只是实际的全局种子增量变得可预测的事实。当然,这可能只是我的测试工具或方法。

根据维基百科:

随机数生成器,特别是对于并行计算机,不应受信任。[12] 强烈建议使用多个RNG检查仿真结果,以检查是否引入了偏差。在并行计算机上使用的推荐生成器包括使用序列拆分的组合线性同余生成器和使用独立序列的滞后斐波那契生成器。

因此,从理论上讲,它应该更好,因为ThreadLocalRandom会创建独立的序列,所以也许我的测试是有缺陷的。

这当然是基于伪随机的。

物理随机性或基于实际熵的安全随机生成器可能会导致差异(即熵更多/更少),但我不是专家,我无法访问一个。

2...

我无法想出一个特定的用例,但一个可能是你使用一个执行器服务,它不断创建和释放线程(假设他们无法控制这一点),但永远不会一次很多(即最多2个并发线程)。您可能会发现 ThreadLocalRandom 比创建单个共享 Random 更昂贵。

鉴于您的评论,另一个原因和可能更好的理由是您可能希望重置所有进程的种子。如果你有一个使用线程的游戏(不是很多,但让我们假装),你可能想要全局重置种子以进行测试,这比尝试将消息传递给所有正在运行的线程要容易得多。

您可能不想使用ThreadLocalRandom的另一个原因是平台原因。某些平台对线程创建和线程本地创建有特定的要求。因此,要解决“你有一个比随机更大的问题”,请查看Google Apps,其中:

Java 应用程序可以创建新线程,但对如何执行此操作有一些限制。这些线程不能“存活”于创建它们的请求。(在后端服务器上,应用程序可以生成一个后台线程,该线程可以“比”创建它的请求“存活”。

为了解决您的其他评论,为什么您将使用无法重用线程的执行器服务:

或者将 com.google.appengine.api.ThreadManager.currentRequestThreadFactory() 返回的工厂对象与 ExecutorService(例如,调用 Executors.newCachedThreadPool(factory))结合使用。

即不一定重用线程的线程池。


答案 2

推荐