【问题标题】:Process M slow calculations on N threads in Java在Java中处理N个线程上的M个慢速计算
【发布时间】:2009-09-18 02:56:12
【问题描述】:

我需要运行 N 个慢速计算(其中 N 是一个相当大的数字)并且希望在 M 个线程上执行此操作,因为慢速计算有大量的 IO 等待时间。我整理了一个小例子,它适用于所有计算都成功的情况。但是,如果计算失败,则期望的行为是停止处理进一步的计算。每个成功的计算都已经将其结果写入数据库,所以我只需要确定哪个计算失败并停止尚未开始的计算。

我的方法是使用 ExecutorService 接口到 Executors.newFixedThreadPool。但是,我没有看到一种明确的方法来识别其中一个计算失败(在我的示例中返回 false)并停止已提交给 ExecutorService 但尚未从池中分配线程的计算。

有没有一种干净的方法可以做到这一点?有更好的方法供我考虑吗?

import java.util.*;
import java.util.concurrent.*;

class Future
{
    static private class MyWorker implements Callable
    {   
        private Integer item;
        public MyWorker(Integer item)
        {
            this.item = item;
        }

        public Boolean call() throws InterruptedException
        {
            if (item == 42) 
            {
                return false;
            }
            else
            {
                System.out.println("Processing: " + item.toString() + " on " + Thread.currentThread().getName());
                Thread.sleep(1000);
                return true;
            }
        }   
    }

    static int NTHREADS = 2;

    public static void main(String args[]) 
    {
        Queue<Integer> numbers = new LinkedList<Integer>();     
        for (int i=1; i<10000; i++)
        {
            numbers.add(i);
        }

        System.out.println("Starting thread test.");

        ExecutorService exec = Executors.newFixedThreadPool(NTHREADS);

        for (Integer i : numbers)
        {
            MyWorker my = new MyWorker(i);
            System.out.println("Submit..." + i.toString());
            exec.submit(my);
            System.out.println("... Done Submit");
        }

        exec.shutdown();

        System.out.println("Exiting thread test.");

    }
}

编辑:这是 afk 建议的有效实现。还是打算看看回调解决方案,希望有其他建议。

import java.util.*;
import java.util.concurrent.*;

class MyFuture
{
    static private class MyWorker implements Callable
    {   
        private Integer item;
        public MyWorker(Integer item)
        {
            this.item = item;
        }

        public Boolean call() 
        {
            if (item == 42) 
            {
                return false;
            }
            else
            {
                System.out.println("Processing: " + item.toString() + " on " + Thread.currentThread().getName());
                try
                {
                    Thread.sleep(1000);
                }
                catch (InterruptedException ie) 
                { 
                // Not much to do here except be grumpy they woke us up...
                } 
                return true;
            }
        }   
    }

    static int NTHREADS = 4;

    public static void main(String args[]) throws InterruptedException
    {
        Queue<Integer> numbers = new LinkedList<Integer>();     
        for (int i=1; i<100; i++)
        {
            numbers.add(i);
        }

        System.out.println("Starting thread test.");

        ExecutorService exec = Executors.newFixedThreadPool(NTHREADS);

        List<Future<Boolean>> futures = new LinkedList<Future<Boolean>>();

        for (Integer i : numbers)
        {
            MyWorker my = new MyWorker(i);
            System.out.println("Submit..." + i.toString());
            Future<Boolean> f = exec.submit(my);
            futures.add(f);
            System.out.println("... Done Submit");
        }

        boolean done = false;

        while (!done)
        {
            Iterator<Future<Boolean>> it = futures.iterator();

            while (it.hasNext()) 
            {
                Future<Boolean> f = it.next();
                if (f.isDone())
                {
                    try
                    {
                        System.out.println("CHECK RETURN VALUE");
                        if (f.get()) 
                        {
                            it.remove();
                        }
                        else
                        {                   
                            System.out.println("IMMEDIATE SHUTDOWN");
                            exec.shutdownNow();
                            done = true;
                            break;
                        }
                    }
                    catch (InterruptedException ie)
                    {
                    }
                    catch (ExecutionException ee)
                    {
                    }
                }
            }
            Thread.sleep(1000);
            if (futures.size() == 0)
            {
                done = true;
            }
        }

        exec.shutdown();

        System.out.println("Exiting thread test.");

    }
}

【问题讨论】:

  • 即使每次迭代都休眠,这仍然是一个“忙等待”,主线程不依赖于信号,而是不断地轮询完成。它会起作用吗?当然。它只是不漂亮。使用 java.util.concurrent 提供的工具,这种低效率是不必要的。
  • @erickson: 主线程应该如何阻塞,等待一个信号或者等待所有的 Future 完成?
  • 发现 ExecutorService.awaitTermination(),问题已解决。

标签: java multithreading


【解决方案1】:

使用回调,I outline in another answer, 可以通知您失败,cancel 所有提交的作业。 (在我的示例中,Callback 实现类可以引用一个Collection,每个Future 都添加到该Future。)对于那些已经完成(或开始,取决于参数的值)的任务@987654327 @ 什么也没做。其余的永远不会开始。

【讨论】:

  • 回调看起来很优雅。我会试试看,但那得等到早上......
  • 我有这个工作的实现,但是一旦提交最后一个作业,主线程就会退出。有没有一种优雅的方式让线程阻塞,直到所有提交的作业都完成,或者回调指示失败?
【解决方案2】:

您可以合并Callable 框架的Future 方面:

 Set futures = new HashSet<Future<Boolean>>
 for (Integer i : numbers)
 {
    MyWorker my = new MyWorker(i);
    System.out.println("Submit..." + i.toString());
    Future<Boolean> f = exec.submit(my);
    futures.add(f);
    System.out.println("... Done Submit");
  }

  for (Future f : futures) {
    if (!f.get().booleanValue()) {
      exec.shutdown();
  }

【讨论】:

  • 如果集合中的最后一个任务是第一个运行的并且很快失败怎么办?在完成其余(不必要的)任务之前不会发现这一点,因为对 get 的调用将阻塞它们的结果。
  • 好点。为了解决这个问题,可以将期货放入发布/订阅队列并在单独的线程中进行测试。
  • 我喜欢这种基本方法,尽管在测试时更改了一些实现细节。使用 LinkedList 因为 Set 以未定义的顺序返回元素,通常返回尚未分配线程的 Future 实例。在调用 get() 之前还要检查 isDone() 以避免等待(我猜这会使设置点有点静音)。最后调用了 shutdownNow() 而不是 shutdown() 因为否则所有提交的计算仍然会完成。将代码发布为对我的问题的编辑。
【解决方案3】:
class Stopper
{
    boolean stopped = false;
    ExecutorService exec;

    public void stop() { if (!stopped) { stopped = true; exec.shutdown(); } }
}

static private class MyWorker implements Callable
{   
    private Integer item;
    private Stopper stopper;
    public MyWorker(Integer item, Stopper stopper)
    {
            this.stopper = stopper;
            this.item = item;
    }

    public Boolean call() throws InterruptedException
    {
            if (item == 42) 
            {
                    stopper.stop();
                    return false;
            }
            else
            {
                    System.out.println("Processing: " + item.toString() + " on " + Thread.currentThread().getName());
                    Thread.sleep(1000);
                    return true;
            }
    }       
}

【讨论】:

  • 这也接近我的想法之一。让我烦恼的是线程需要知道它的执行容器。不过,这肯定会解决问题。
  • 停止标志应该是volatile,因为它被多个线程访问。但这是一个有争议的问题,因为任何已经提交的任务仍然会被执行,即使在关闭调用之后。
猜你喜欢
  • 2015-01-29
  • 1970-01-01
  • 1970-01-01
  • 2018-03-11
  • 2015-10-05
  • 2012-04-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多