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();
}

...
}

对我来说,这似乎是新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只能从同步块访问。

注意,这也允许在没有额外同步的情况下集成遗留的ThreadLocalRandom.current()。它甚至可以与像Random r = new Random(); doTheWork(ThreadLocal.withInitial(() -> r));这样的同步RNG一起工作。

当然,这只是为了说明,因为所讨论的rng有专门的方法来创建流,这些流可以在工作负载移交给另一个工作线程之前分裂。

对这个问题最直接的回答是,新的Java 17 api没有提供任何直接支持来获得RandomGenerator的高效、线程安全的实现。

使用这个答案的思想,下面的代码展示了如何使用新的SplittableGenerator接口来实现它,但是它是大量的样板文件。

import java.util.random.RandomGenerator;
public final class ThreadSafeGenerator implements RandomGenerator {
private final ThreadLocal<RandomGenerator> threadLocal;
public ThreadSafeGenerator(String name) {
SplittableGenerator baseGenerator = SplittableGenerator.of(name);
threadLocal = ThreadLocal.withInitial(() -> {
synchronized (baseGenerator) {
return baseGenerator.split();
}
});
}
@Override
public boolean nextBoolean() {
return threadLocal.get().nextBoolean();
}
@Override
public int nextInt() {
return threadLocal.get().nextInt();
}
// Not finished. All methods of RandomGenerator should be implemented following this pattern.
}