在模拟的并行执行中找到正确的随机种子数

时间:2018-01-02 15:50:30

标签: java random refactoring

我有一个这样的课程:

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变量,都能得到相同的结果吗?

1 个答案:

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