Java 17 中的线程安全 RandomGenerator

cpp*_*ner 2 java random multithreading java-17

Java 17 添加了新的RandomGenerator接口。然而,似乎所有新的实现都不是线程安全的。在多线程情况下使用新接口的推荐方法是在生成新线程时使用原始线程SplittableRandom并进行调用。split但是,在某些情况下,您无法控制生成新线程的代码部分,而只需在多个线程之间共享一个实例。

我可以使用Random,但这会因为所有同步而导致争用。也可以使用ThreadLocalRandom,但我不愿意这样做,因为这个类现在被认为是“遗留的RandomGenerator”,并且因为这不会给我一个没有全部样板负载的线程安全实现:

 new RandomGenerator() {
    
    @Override 
    public int nextInt() {
      return ThreadLocalRandom.current().nextInt();
    }
    
    @Override
    public long nextLong() {
      return ThreadLocalRandom.current().nextLong();
    }
    
    ...
}
Run Code Online (Sandbox Code Playgroud)

对我来说,这似乎是新 API 中相当基本的差距,但我可能会遗漏一些东西。获得线程安全实现的惯用 Java 17 方法是什么RandomGenerator

Hol*_*ger 7

当您无法控制工作拆分或线程创建时,从使用站点 xe2x80x99s 的角度来看,最简单的解决方案是ThreadLocal<RandomGenerator>.

\n
public static void main(String[] args) {\n    // spin up threads\n    ForkJoinPool.commonPool().invokeAll(\n        Collections.nCopies(8, () -> { Thread.sleep(300); return null; }));\n\n    doWork(ThreadLocal.withInitial(synching(SplittableGenerator.of("L32X64MixRandom"))));\n    doWork(ThreadLocal.withInitial(synching(new SplittableRandom())));\n    doWork(ThreadLocal.withInitial(ThreadLocalRandom::current));\n}\n\nstatic final Supplier<SplittableGenerator> synching(SplittableGenerator r) {\n    return () -> {\n        synchronized(r) {\n            return r.split();\n        }\n    };\n}\n\nprivate static void doWork(ThreadLocal<RandomGenerator> theGenerator) {\n    System.out.println(theGenerator.get().toString());\n    Set<Thread> threads = ConcurrentHashMap.newKeySet();\n    var ints = Stream.generate(() -> theGenerator.get().nextInt(10, 90))\n        .parallel()\n        .limit(100)\n        .peek(x -> threads.add(Thread.currentThread()))\n        .toArray();\n    System.out.println(Arrays.toString(ints));\n    System.out.println(threads.stream().map(Thread::getName).toList());\n    System.out.println();\n}\n
Run Code Online (Sandbox Code Playgroud)\n

由于这不会在将 RNG 移交给另一个线程之前拆分 RNG,而是从现有的工作线程中拆分,因此它必须同步操作。但是,当第一次查询线程局部变量时,每个线程只会发生一次。还值得注意的是,基本 RNG 只能从该同步块访问。

\n

请注意,这还允许集成遗留系统而ThreadLocalRandom.current()无需额外的同步。它甚至可以与同步 RNG 一起使用,例如Random r = new Random(); doTheWork(ThreadLocal.withInitial(() -> r));.

\n

当然,\xe2\x80\x99s 仅用于说明,因为所讨论的 RNG 有专门的方法来创建流,这些流可以在工作负载移交给另一个工作线程之前进行拆分。

\n