如何从多个线程获取第一个结果并取消剩余

时间:2016-05-27 11:15:55

标签: java multithreading

我有请求和多线程,它们以不同的方式寻找结果,每个线程应该在某个时候得到一些结果..我需要采取< strong>第一个完成主题的结果,返回此结果并 kill 所有剩余线程。当我返回一些默认结果时,我也有超时。

我能想到两个解决方案,对我来说似乎都没有“正确”。

1)循环完成任务,询问它们是否完成,睡了一会儿并返回它创建的第一个完成的任务..

import java.util.ArrayList;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.apache.log4j.LogManager;

public class App {    
    public static final ExecutorService executors = Executors.newCachedThreadPool();

    public static void main(String[] args) {
        ArrayList<Future<String>> taskList = new ArrayList<>();
        taskList.add(executors.submit(new Task1()));
        taskList.add(executors.submit(new Task2()));

        String result = null;
        long start = System.currentTimeMillis();
        long timeout = 1000;

        while ((start + timeout) < System.currentTimeMillis()) {
            try {
                for (Future<String> future : taskList) {
                    if (future.isDone()) {
                        result = future.get();
                        break;
                    }
                }

                if (result != null) {
                    break;
                }

                Thread.sleep(10);
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        }

        if (result == null) {
            result = "default..";
        }
    }

}

class Task1 implements Callable<String> {
    @Override
    public String call() throws Exception {
        // find result in one way
        return new String("result..");
    }
}

class Task2 implements Callable<String> {
    @Override
    public String call() throws Exception {
        // find result in some other way
        return new String("result..");
    }
}

2)通过调用future.get(timeout, TimeUnit.MILLISECONDS);监视每个任务与另一个线程,然后第一个完成的线程将为所有其他线程调用future.cancel(true); ...

在我看来,第一个解决方案就像浪费处理器时间,而第二个解决方案在我看来就像浪费线程一样..

最后,问题是:有更好的解决方案吗?

提前谢谢

编辑:谢谢大家的回答,我用“John H”的回答解决了这个问题:

  

有一个内置函数可以执行此操作:https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ExecutorService.html#invokeAny%28java.util.Collection,%20long,%20java.util.concurrent.TimeUnit%29

     

这将调用您提供的所有任务,并等待第一个任务返回一个时间限制的答案。如果它们都没有及时返回结果,则可以捕获TimeoutException并返回默认值。否则,您可以使用它返回的第一个值,它将负责取消其余任务。

3 个答案:

答案 0 :(得分:1)

共享CountDownLatch怎么样?

您的主线程将在cdl.await(30, TimeUnit.SECONDS);等待,工作线程将在完成后调用cdl.countDown();

另一种选择是使用共享Exchanger,这样您就可以轻松检索结果。尽管使用交换器两个工作线程可能会在它们之间交换结果的可能性很小,但一个简单的解决方法是检查工作线程是否检索“dummyString”,因此知道它是获得结果的主线程。

主线程:

myExchanger.exchange("dummyString", 30, TimeUnit.SECONDS);

工作线程:

while(!myExchanger.exchange(result).equals("dummyString"));

答案 1 :(得分:1)

有一个内置函数可以执行此操作:https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ExecutorService.html#invokeAny%28java.util.Collection,%20long,%20java.util.concurrent.TimeUnit%29

这将调用您提供的所有任务,并等待第一个任务返回一个时间限制的答案。如果它们都没有及时返回结果,则可以捕获TimeoutException并返回默认值。否则,您可以使用它返回的第一个值,它将负责取消其余任务。

答案 2 :(得分:0)

您可以考虑SynchronousQueue

主线程启动所有线程,为每个线程提供对队列的引用,然后在队列上执行take(这将阻塞直到工作线程执行put)。发现结果的第一个线程发布到队列,释放主线程。

主线程然后迭代所有工人取消它们。

使用等待超时的线程可以实现默认值,put是默认值。

这也适用于Runnable s。

示例代码 - 似乎有效但cancel没有。

public static void main(String[] args) {
  try {
    ArrayList<Future<String>> taskList = new ArrayList<>();
    BlockingQueue q = new SynchronousQueue();
    taskList.add(executors.submit(new Task1(q)));
    taskList.add(executors.submit(new Task2(q)));

    Object took = q.take();
    for (Future<String> task : taskList) {
      if (!task.isDone()) {
        task.cancel(true);
      }
    }
    System.out.println("Got " + took);
  } catch (InterruptedException ex) {
    Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
  }
}

似乎cancel是不合适的。有关替代方案,请参阅here