在 Java 中动态地将工作负载分配给多个线程

Arm*_*man 5 java parallel-processing multithreading montecarlo

假设我有 5 个线程,它们必须1,000,000对并行蒙特卡罗方法程序进行总计函数调用。我1,000,000 / 5为 5 个线程中的每一个分配了函数调用。然而,经过多次测试(一些测试的迭代次数高达 1 万亿次),我意识到某些线程的完成速度比其他线程快得多。因此,我想动态地将工作负载分配给每个线程。我的第一个方法涉及一个AtomicLong初始值设置为 10 亿的变量。在每次函数调用之后,我会将 减AtomicLong1。在每次函数调用之前,程序都会检查 是否AtomicLong大于0,如下所示:

AtomicLong remainingIterations = new AtomicLong(1000000000);
ExecutorService threadPool = Executors.newFixedThreadPool(5);

for (int i = 0; i < 5; i++) {//create 5 threads
   threadPool.submit(new Runnable() {
       public void run() {
          while (remainingIterations.get() > 0) {//do a function call if necessary 
             remainingIterations.decrementAndGet();//decrement # of remaining calls needed
             doOneFunctionCall();//perform a function call
          }
       }
   });
}//more unrelated code is not show (thread shutdown, etc.)
Run Code Online (Sandbox Code Playgroud)

这种方法似乎非常慢,我正确使用 AtomicLong 吗?有更好的方法吗?

And*_*ert 3

我正确使用 AtomicLong 吗?

不完全的。按照您使用它的方式,两个线程可以各自检查remainingIterations,各自查看1,然后各自递减它,使您总计-1

至于速度缓慢的问题,如果完成得很快,您的应用程序可能doOneFunctionCall()会因 AtomicLong 周围的锁争用而陷入困境。

ExecutorService 的好处在于,它在逻辑上将正在完成的工作与正在执行该任务的线程解耦。您可以提交比线程数更多的作业,ExecutorService 将尽快执行它们:

ExecutorService threadPool = Executors.newFixedThreadPool(5);

for (int i = 0; i < 1000000; i++) {
   threadPool.submit(new Runnable() {
       public void run() {
           doOneFunctionCall();
       }
   });
}
Run Code Online (Sandbox Code Playgroud)

这可能会在另一个方向上过度平衡您的工作:创建太多短暂的 Runnable 对象。您可以尝试看看什么可以在分配工作和快速执行工作之间实现最佳平衡:

ExecutorService threadPool = Executors.newFixedThreadPool(5);

for (int i = 0; i < 1000; i++) {
   threadPool.submit(new Runnable() {
       public void run() {
           for (int j = 0; j < 1000; j++) {
               doOneFunctionCall();
           }
       }
   });
}
Run Code Online (Sandbox Code Playgroud)