Java 使用并行流返回提供的最快值

Java 使用并行流返回提供的最快值,java,multithreading,parallel-processing,java-8,java-stream,Java,Multithreading,Parallel Processing,Java 8,Java Stream,我有一组供应商,他们都提供相同的结果,但速度不同(并且变化) 我想要一种优雅的方式来同时启动供应商,一旦其中一个供应商产生了价值,就返回它(丢弃其他结果) 我已经尝试使用并行流和Stream.findAny()来实现这一点,但在生成所有结果之前,它似乎总是阻塞 下面是一个单元测试,演示了我的问题: import org.junit.Test; import java.util.Collections; import java.util.Optional; import java.util.Se

我有一组供应商,他们都提供相同的结果,但速度不同(并且变化)

我想要一种优雅的方式来同时启动供应商,一旦其中一个供应商产生了价值,就返回它(丢弃其他结果)

我已经尝试使用并行流和
Stream.findAny()
来实现这一点,但在生成所有结果之前,它似乎总是阻塞

下面是一个单元测试,演示了我的问题:

import org.junit.Test;

import java.util.Collections;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
import java.util.stream.Stream;

import static org.junit.Assert.*;

public class RaceTest {

    @Test
    public void testRace() {
        // Set up suppliers
        Set<Supplier<String>> suppliers = Collections.newSetFromMap(new ConcurrentHashMap<>());
        suppliers.add(() -> "fast"); // This supplier returns immediately
        suppliers.add(() -> {
            try {
                Thread.sleep(10_000);
                return "slow";
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }); // This supplier takes 10 seconds to produce a value

        Stream<Supplier<String>> stream = suppliers.parallelStream();
        assertTrue(stream.isParallel()); // Stream can work in parallel
        long start = System.currentTimeMillis();
        Optional<String> winner = stream
                .map(Supplier::get)
                .findAny();
        long duration = System.currentTimeMillis() - start;
        assertTrue(winner.isPresent()); // Some value was produced
        assertEquals("fast", winner.get()); // The value is "fast"
        assertTrue(duration < 9_000); // The whole process took less than 9 seconds
    }
}
import org.junit.Test;
导入java.util.Collections;
导入java.util.Optional;
导入java.util.Set;
导入java.util.concurrent.ConcurrentHashMap;
导入java.util.function.Supplier;
导入java.util.stream.stream;
导入静态org.junit.Assert.*;
公营赛马测试{
@试验
public void testRace(){
//建立供应商
Set suppliers=Collections.newSetFromMap(新的ConcurrentHashMap());
suppliers.add(()->“fast”);//此供应商立即返回
供应商。添加(()->{
试一试{
睡眠(10_000);
返回“慢”;
}捕捉(中断异常e){
抛出新的运行时异常(e);
}
});//此供应商需要10秒钟才能生成一个值
Stream=suppliers.parallelStream();
assertTrue(stream.isParallel());//流可以并行工作
长启动=System.currentTimeMillis();
可选赢家=流
.map(供应商::获取)
.findAny();
长持续时间=System.currentTimeMillis()-开始;
assertTrue(winner.isPresent());//产生了一些值
assertEquals(“fast”,winner.get());//值为“fast”
assertTrue(持续时间<9_000);//整个过程不到9秒
}
}
测试的结果是最后一个断言失败,因为整个测试大约需要10秒才能完成


我在这里做错了什么?

流API不适用于这样的事情,因为它不能保证任务何时完成。更好的解决方案是使用
CompletableFuture

long start = System.currentTimeMillis();
String winner = CompletableFuture
        .anyOf(suppliers.stream().map(CompletableFuture::supplyAsync)
                .toArray(CompletableFuture[]::new)).join().toString();
long duration = System.currentTimeMillis() - start;
assertEquals("fast", winner); // The value is "fast"
assertTrue(duration < 9_000); // The whole process took less than 9 seconds
long start=System.currentTimeMillis();
字符串赢家=可完成的未来
.anyOf(suppliers.stream().map(CompletableFuture::SupplySync)
.toArray(CompletableFuture[]::new)).join().toString();
长持续时间=System.currentTimeMillis()-开始;
assertEquals(“快速”,赢家);//值为“快速”
资产真实(持续时间<9_000);//整个过程不到9秒
请注意,如果通用FJP没有足够的并行级别,它仍然可能不会并行启动所有供应商。要解决此问题,您可以创建自己的池,该池具有所需的并行级别:

long start = System.currentTimeMillis();
ForkJoinPool fjp = new ForkJoinPool(suppliers.size());
String winner = CompletableFuture
        .anyOf(suppliers.stream().map(s -> CompletableFuture.supplyAsync(s, fjp))
                .toArray(CompletableFuture[]::new)).join().toString();
long duration = System.currentTimeMillis() - start;
assertEquals("fast", winner); // The value is "fast"
assertTrue(duration < 9_000); // The whole process took less than 9 seconds
fjp.shutdownNow();
long start=System.currentTimeMillis();
ForkJoinPool fjp=新的ForkJoinPool(suppliers.size());
字符串赢家=可完成的未来
.anyOf(suppliers.stream().map(s->CompletableFuture.supplyAsync(s,fjp))
.toArray(CompletableFuture[]::new)).join().toString();
长持续时间=System.currentTimeMillis()-开始;
assertEquals(“快速”,赢家);//值为“快速”
资产真实(持续时间<9_000);//整个过程不到9秒
fjp.shutdownNow();

您当前使用的代码是不确定的。引用以下文件的Javadoc:

此操作的行为明显不确定;可以自由选择流中的任何元素

您可以使用并向其提交所有任务。然后,将返回第一个完成任务的
未来

long start = System.currentTimeMillis();
ExecutorService executor = Executors.newFixedThreadPool(suppliers.size());
CompletionService<String> completionService = new ExecutorCompletionService<>(executor);
suppliers.forEach(s -> completionService.submit(() -> s.get()));
String winner = completionService.take().get();
long duration = System.currentTimeMillis() - start;
assertEquals("fast", winner); // The value is "fast"
assertTrue(duration < 9_000); // The whole process took less than 9 seconds
long start=System.currentTimeMillis();
ExecutorService executor=Executors.newFixedThreadPool(suppliers.size());
CompletionService CompletionService=新的执行者CompletionService(执行者);
suppliers.forEach->completionService.submit(()->s.get());
字符串winner=completionService.take().get();
长持续时间=System.currentTimeMillis()-开始;
assertEquals(“快速”,赢家);//值为“快速”
资产真实(持续时间<9_000);//整个过程不到9秒

在这种情况下,最好使用
Callable
而不是
Supplier
(相同的功能签名),并使用自Java 5以来存在的良好的旧并发API:

Set<Callable<String>> suppliers=new HashSet<>();
suppliers.add(() -> "fast"); // This supplier returns immediately
suppliers.add(() -> {
        Thread.sleep(10_000);
        return "slow";
    }
);

ExecutorService es=Executors.newCachedThreadPool();
try {

    String result = es.invokeAny(suppliers);
    System.out.println(result);

} catch (InterruptedException|ExecutionException ex) {
    Logger.getLogger(MyClass.class.getName()).log(Level.SEVERE, null, ex);
}
es.shutdown();
Set suppliers=new HashSet();
供应商。添加(()->“fast”);//该供应商立即返回
供应商。添加(()->{
睡眠(10_000);
返回“慢”;
}
);
ExecutorService es=Executors.newCachedThreadPool();
试一试{
字符串结果=es.invokeAny(供应商);
系统输出打印项次(结果);
}捕获(InterruptedException | ExecutionException ex){
Logger.getLogger(MyClass.class.getName()).log(Level.SEVERE,null,ex);
}
es.shutdown();
请注意,整个“全部运行并以最快的速度返回”如何变成单个方法调用


它还有一个好处,就是一旦有一个结果可用,就会取消/中断所有挂起的操作,因此缓慢的操作实际上不会在这里等待整整十秒钟(好吧,在大多数情况下,因为时间是不确定的)。

我刚刚测试了您的代码,它没有失败(这里是JDK 1.8.051)。但是
findAny
是不确定的,所以这只是运气。如果您将快速供应商添加4次(set size=5),它会按预期工作,我怀疑当流太小时,它会恢复为顺序执行。这里不需要源代码是
并发的…
集合。只要在操作过程中不进行修改,任何集合都可以。事实上,由于其内部工作方式,普通集合在执行此任务时表现更好,即使是在并行执行时也是如此。但正如其他人所说,
Stream
API在这里不是合适的工具。
invokeAny
的Javadoc说“执行给定的任务,返回成功完成的任务的结果”。这是否意味着