Java 8并行Stream findFirst

时间:2014-12-20 05:37:33

标签: java parallel-processing java-8

假设我们有一个像这样的工人列表:

List<Worker> workers = new ArrayList<>();
workers.add(new Worker(1));
workers.add(new Worker(2));
workers.add(new Worker(3));
workers.add(new Worker(4));
workers.add(new Worker(5));

我想找到第一个完成工作的工人,所以:

Worker first = workers.parallelStream().filter(Worker::finish).findFirst().orElse(null);

但是有一个问题,我不想等所有工人完成工作然后找到第一个,但第一个工人一旦他完成工作

public class Test {

    public static void main(String[] args) {
        List<Worker> workers = new ArrayList<>();
        workers.add(new Worker(1));
        workers.add(new Worker(2));
        workers.add(new Worker(3));
        workers.add(new Worker(4));
        workers.add(new Worker(5));
        Worker first = workers.parallelStream().filter(Worker::finish).findFirst().orElse(null);
        if (first != null) {
            System.out.println("id : " + first.id);
        }
    }

    static class Worker {

        int id;

        Worker(int id) {
            this.id = id;
        }

        boolean finish() {
            int t = id * 1000;
            System.out.println(id + " -> " + t);
            try {
                Thread.sleep(t);
            } catch (InterruptedException ignored) {
            }
            return true;
        }

    }

}

有没有办法使用java.util.Stream实现它?

感谢。

4 个答案:

答案 0 :(得分:3)

您似乎对Stream存在严重误解。 Stream并不意味着要发动工人。事实上,如果你使用findFirst,它可能会发生它没有工人而是第一个工人。所以它也不会等待“让所有工人完成”,而只是等待当前待定的线程。但是,由于您有一个相当小的流,因此可能已经启动了所有工作者,因为您的环境中有多少线程可用。但这不是一种保证行为。

请注意,如果您使用顺序流而不是并行流,那么它肯定会仅处理第一个项目(因为它返回true)而不会处理另一个项目。但由于流实现无法预测结果,因此它会尊重您通过并行执行“加速”操作的请求,并可能使用更多线程提前开始处理更多项目。

答案 1 :(得分:2)

当您使用finish方法作为Stream的过滤器时,这意味着为了评估过滤器对特定Worker的谓词,Worker必须完成其工作。

但是,当您将此代码作为并行Stream运行时,过滤器可能会同时应用于多个Worker,在这种情况下,第一个完成将为您提供输出。但是,您无法控制并行Stream将使用的线程数。它可能决定某些Worker应该在同一个线程上处理,在这种情况下,其中一些将不会被处理(因为你的终端操作只需要一个Worker完成它的处理)。

因此,如果您的目标是同时为所有工作人员执行finish,则不能使用Stream(甚至不是并行流)。

答案 2 :(得分:0)

我知道这是一个老问题,但是在这里找到了一些不错的解决方案: https://winterbe.com/posts/2015/04/07/java8-concurrency-tutorial-thread-executor-examples/

InvokeAny 下:

  

批量提交可调用对象的另一种方法是方法invokeAny()   它与invokeAll()略有不同。而不是返回   该方法的未来对象,直到第一个可调用终止   并返回该可调用对象的结果。

将流更改为可调用集合。看起来真的很干净。

答案 3 :(得分:-1)

您可以尝试使用Reactive Extensions for Java(Stream)中的Observable,而不是使用RxJava。下面的示例代码。

public class Example {
  public static void main(String[] args) {
    Maybe<Worker> workerResult = Observable.fromArray(Worker.run(1), Worker.run(2), Worker.run(3), Worker.run(4), Worker.run(5))
        .flatMap(worker -> (Observable<Worker>) worker)
        .firstElement();
    workerResult.subscribe(onNext -> System.out.println("First worker [" + onNext.toString() + "]"));

    try {
      Thread.sleep(5000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}

class Worker {
  private int id;
  static Observable run(int id) { return Observable.just(new Worker(id)).observeOn(Schedulers.computation()).doOnNext(Worker::process); }
  private Worker(int id) { this.id = id; }
  public void process() {
    try {
      Thread.sleep(new Random().nextInt(2000));
    } catch (InterruptedException e) {
      System.out.println(String.format("[%s] Thread interrupted [%s]", Thread.currentThread(), id));
    }
    System.out.println(String.format("[%s] Worker [%s]", Thread.currentThread(), id));
  }
    public String toString() { return "Worker [" + id + "]"; }
}

示例输出:

[Thread[RxComputationThreadPool-2,5,main]] Worker [2]
[Thread[RxComputationThreadPool-1,5,main]] Thread interrupted [1]
[Thread[RxComputationThreadPool-1,5,main]] Worker [1]
[Thread[RxComputationThreadPool-4,5,main]] Thread interrupted [4]
[Thread[RxComputationThreadPool-3,5,main]] Thread interrupted [3]
[Thread[RxComputationThreadPool-3,5,main]] Worker [3]
[Thread[RxComputationThreadPool-4,5,main]] Worker [4]
First worker [Worker [2]]