我有一个这样的课程:
public class Class implements Runnable {
...
public void run() {
ArrayList<Integer> simulations = new ArrayList<>();
ArrayList<Random> randomSeedsList = new ArrayList<>();
for (int run = 0; run < getNumberOfSimulations(); run++) {
simulations.add(run);
Random random = new Random(run);
randomSeedsList.add(run, random);
}
...
simulations.parallelStream().forEach(run -> runSimulation(run,...))
}
}
现在,runSimulation
执行很多操作,调用/创建其他类等等,并且在许多执行点(甚至在其他类的内部),调用此函数以获取下一个随机数根据您所在的特定运行/模拟:
rantInt(arg1,arg2,run)
rantInt在下面的课程中定义:
public class Util {
// get random integer between min and max (inclusive)
public static int randInt(int min, int max, int run) {
return Class.randomSeedsList.get(run).nextInt((max - min) + 1) + min;
}
}
问题是,我希望每次运行时都要说N个并行模拟,每个人都有完全相同的结果,而且我已经得到了这个实现 - 但它很丑,因为我必须通过{{1变量通过大量的函数调用和类构造函数,这样当我们在这些模拟的代码深处执行某些函数run
时,我就会知道它的值。
我的问题:有更优雅的方法吗?我在这个实现中遗漏的任何其他设计原则,无需在任何地方携带rantInt
变量,都能得到相同的结果吗?
答案 0 :(得分:1)
您可以将Random
实例与执行模拟的当前线程相关联。由于模拟和线程之间没有1:1的关系,因此应将此关联封装在专用的管理器类中,该类强制执行线程局部变量的初始化和清理并拒绝无效的使用:
public class RandomManager {
private static ThreadLocal<Random> RANDOMS = new ThreadLocal<>();
public static void withRandom(Random rnd, Runnable run) {
Objects.requireNonNull(rnd);
Objects.requireNonNull(run);
if(RANDOMS.get() != null) throw new IllegalStateException("nested invocation");
RANDOMS.set(rnd);
try {
run.run();
}
finally {
RANDOMS.remove();
}
}
// get random integer between min and max (inclusive)
public static int randInt(int min, int max) {
Random r = RANDOMS.get();
if(r == null) throw new IllegalStateException("not within withRandom(...)");
return r.nextInt((max - min) + 1) + min;
}
}
这样,当前正在执行模拟的每个线程都可以与Random
实例相关联,而拒绝从未执行模拟的线程访问Random
的尝试,并且线程可以执行多个模拟一个接一个,每个都与不同的Random
实例相关联,但您可能无法嵌套模拟。检测并拒绝嵌套执行的尝试。另一个重要的特性是之后删除了线程本地值,因此如果线程被重用于其他目的,则不会留下任何垃圾,这适用于并行流使用的工作线程。
Random
实例的管理与实际模拟分离,只需使用任意Runnable
来描述可能要求本地随机值的操作。
然后,使用代码可能是
class YourClass implements Runnable {
public void run() {
ArrayList<Random> randomSeedsList = new ArrayList<>();
int total = getNumberOfSimulations();
for (int run = 0; run < total; run++) {
randomSeedsList.add(new Random(run));
}
assert randomSeedsList.size() == total;
IntStream.range(0, total).parallel()
.forEach(run -> RandomManager.withRandom(randomSeedsList.get(run),
()->runSimulation(run, …)));
}
}
或者,如果仅存在整数索引以维持与Random
实例的关联(这不再是必需的),则可以进一步简化此操作:
class YourClass implements Runnable {
public void run() {
List<Random> randomSeedsList
= IntStream.range(0, getNumberOfSimulations())
.mapToObj(Random::new)
.collect(Collectors.toList());
randomSeedsList.parallelStream()
.forEach(random -> RandomManager.withRandom(random,
()->runSimulation(…)));
}
}