Java 17 添加了一个新的 RandomGenerator
接口(interface)。然而,似乎所有的新实现都不是线程安全的。在多线程情况下使用新接口(interface)的推荐方法是使用 SplittableRandom
并在生成新线程时从原始线程调用 split
。但是,在某些情况下,您无法控制生成新线程的代码部分,您只需要在多个线程之间共享一个实例。
我可以使用 Random
但这会导致争用,因为所有的同步。也可以使用 ThreadLocalRandom
,但我不愿意这样做,因为这个类现在被认为是“legacy”,而且因为这没有给我一个线程安全的实现RandomGenerator
没有全部样板文件:
new RandomGenerator() {
@Override
public int nextInt() {
return ThreadLocalRandom.current().nextInt();
}
@Override
public long nextLong() {
return ThreadLocalRandom.current().nextLong();
}
...
}
对我来说,这似乎是新 API 中一个相当根本的差距,但我可能遗漏了一些东西。获得 RandomGenerator
的线程安全实现的惯用 Java 17 方法是什么?
最佳答案
当您无法控制线程的工作拆分或创建时,从使用站点的角度来看,最简单的解决方案是 ThreadLocal<RandomGenerator>
.
public static void main(String[] args) {
// spin up threads
ForkJoinPool.commonPool().invokeAll(
Collections.nCopies(8, () -> { Thread.sleep(300); return null; }));
doWork(ThreadLocal.withInitial(synching(SplittableGenerator.of("L32X64MixRandom"))));
doWork(ThreadLocal.withInitial(synching(new SplittableRandom())));
doWork(ThreadLocal.withInitial(ThreadLocalRandom::current));
}
static final Supplier<SplittableGenerator> synching(SplittableGenerator r) {
return () -> {
synchronized(r) {
return r.split();
}
};
}
private static void doWork(ThreadLocal<RandomGenerator> theGenerator) {
System.out.println(theGenerator.get().toString());
Set<Thread> threads = ConcurrentHashMap.newKeySet();
var ints = Stream.generate(() -> theGenerator.get().nextInt(10, 90))
.parallel()
.limit(100)
.peek(x -> threads.add(Thread.currentThread()))
.toArray();
System.out.println(Arrays.toString(ints));
System.out.println(threads.stream().map(Thread::getName).toList());
System.out.println();
}
由于这不会在将一个 RNG 移交给另一个线程之前拆分 RNG,而是从已经存在的工作线程中进行拆分,因此它必须同步操作。但是当第一次查询线程局部变量时,每个线程只发生一次。还值得注意的是,基础 RNG 只能从该同步块(synchronized block)访问。
请注意,这也允许集成旧版 ThreadLocalRandom.current()
没有额外的同步。它甚至可以与 Random r = new Random(); doTheWork(ThreadLocal.withInitial(() -> r));
这样的同步 RNG 一起使用.
当然,这只是为了说明,因为所讨论的 RNG 有专门的方法来创建流,这些流可以在工作负载移交给另一个工作线程之前进行拆分。
https://stackoverflow.com/questions/73021220/