Java8 - idiomatic way to process a Stream> in parallel delivering to a non-thread-safe consumer?
假设我有
将此转换为
即并行处理流但按顺序传送输出(随机顺序ok,只要它是单线程的)。
我知道我可以通过在原始流和消费者之间设置
您仍然可以使用
1 2 3 4 5 6 7 8 9 | ExecutorService service = Executors.newFixedThreadPool(4); stream.map(c -> service.submit(c)).map(future -> { try { return future.get(); //retrieve callable result } catch (InterruptedException | ExecutionException ex) { //Exception handling throw new RuntimeException(ex); } }); |
您可以按顺序进一步处理生成的
如果直接在
如果您想按照它们可用的确切顺序处理可调用的结果,则必须使用
编辑:
在流中使用
我找到了一种可能的副作用更重的解决方案,将
我使用类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | class TaskMapper implements Function<Callable<Integer>, List<Future<Integer>>>{ private final ExecutorService service; private final int chunkSize; private List<Future<Integer>> chunk = new ArrayList<>(); TaskMapper(ExecutorService service, int chunkSize){ this.service = service; this.chunkSize = chunkSize; } @Override public List<Future<Integer>> apply(Callable<Integer> c) { chunk.add(service.submit(c)); if(chunk.size() == chunkSize){ List<Future<Integer>> fList = chunk; chunk = new ArrayList<>(); return fList; }else{ return null; } } List<Future<Integer>> getChunk(){ return chunk; } } |
这就是流操作链的样子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | ExecutorService service = Executors.newFixedThreadPool(4); TaskMapper taskMapper = new TaskMapper(service, 4); stream.map(taskMapper) .filter(fl -> fl != null) //filter for the chunks .flatMap(fl -> fl.stream()) //flat-map the chunks to futures .map(future -> { try { return future.get(); } catch (InterruptedException | ExecutionException ex) { throw new RuntimeException(ex); } }); //process the remaining futures for(Future<Integer> f : taskMapper.getChunk()){ try { Integer i = f.get(); //process i } catch (InterruptedException | ExecutionException ex) { //exception handling } } |
其工作原理如下:
这个构造适用于我执行的测试,但我知道由于流的副作用,状态和未定义的评估行为,它可能是脆弱的。
EDIT2:
我使用自定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | public class ExecutorServiceSpliterator< T > extends AbstractSpliterator<Future< T >>{ private final Spliterator<? extends Callable< T >> srcSpliterator; private final ExecutorService service; private final int chunkSize; private final Queue<Future< T >> futures = new LinkedList<>(); private ExecutorServiceSpliterator(Spliterator<? extends Callable< T >> srcSpliterator) { this(srcSpliterator, Executors.newFixedThreadPool(8), 30); //default } private ExecutorServiceSpliterator(Spliterator<? extends Callable< T >> srcSpliterator, ExecutorService service, int chunkSize) { super(Long.MAX_VALUE, srcSpliterator.characteristics() & ~SIZED & ~CONCURRENT); this.srcSpliterator = srcSpliterator; this.service = service; this.chunkSize = chunkSize; } public static < T > Stream< T > pipeParallelized(Stream<? extends Callable< T >> srcStream){ return getStream(new ExecutorServiceSpliterator<>(srcStream.spliterator())); } public static < T > Stream< T > pipeParallelized(Stream<? extends Callable< T >> srcStream, ExecutorService service, int chunkSize){ return getStream(new ExecutorServiceSpliterator<>(srcStream.spliterator(), service, chunkSize)); } private static < T > Stream< T > getStream(ExecutorServiceSpliterator< T > serviceSpliterator){ return StreamSupport.stream(serviceSpliterator, false) .map(future -> { try { return future.get(); } catch (InterruptedException | ExecutionException ex) { throw new RuntimeException(ex); } } ); } @Override public boolean tryAdvance(Consumer<? super Future< T >> action) { boolean didAdvance = true; while((didAdvance = srcSpliterator.tryAdvance(c -> futures.add(service.submit(c)))) && futures.size() < chunkSize); if(!didAdvance){ service.shutdown(); } if(!futures.isEmpty()){ Future< T > future = futures.remove(); action.accept(future); return true; } return false; } } |
这个类提供了函数(
1 | ExecutorServiceSpliterator.pipeParallelized(stream); |
这一行采用
没有其他答案对我有用。
我终于找到了这样的东西(伪代码):
1 2 3 4 5 6 7 | ExecutorService executor = Executors.newWorkStealingPool(); CompletionService completor = new CompletionService(executor); int count = stream.map(completor::submit).count(); while(count-- > 0) { SomeClass obj = completor.take(); consume(obj); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | public static void main(String[] args) { testInfititeCallableStream(); } private static void testInfititeCallableStream() { ExecutorService service = Executors.newFixedThreadPool(100); Consumer<Future<String>> consumeResult = (Future<String> future)->{ try { System.out.println(future.get()); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } }; getCallableStream().parallel().map(callable -> service.submit(callable)).forEach(consumeResult); } private static Stream<Callable<String>> getCallableStream() { Random randomWait = new Random(); return Stream.<Callable<String>>generate(() -> new Callable<String>() { public String call() throws Exception { //wait for testing long time = System.currentTimeMillis(); TimeUnit.MILLISECONDS.sleep(randomWait.nextInt(5000)); return time +":" +UUID.randomUUID().toString(); }; }).limit(Integer.MAX_VALUE); } |
第一个例子:
1 2 3 4 5 6 7 8 9 10 |
第二个例子:
1 2 3 4 | Stream.of("1","2","3","4","","5") .filter(s->s.length() > 0) .parallel() .forEachOrdered(System.out::println); |
你在寻求一种惯用的解决方案。不鼓励在其行为参数中具有副作用的流(在Stream的javadoc中明确说明)。
因此惯用解决方案基本上是ExecutorService + Futures和一些循环/ forEach()。如果您有一个Stream作为参数,只需将其转换为具有标准收集器的List。
像这样的东西:
1 2 3 4 | ExecutorService service = Executors.newFixedThreadPool(5); service.invokeAll(callables).forEach( doSomething ); // or just return service.invokeAll(callables); |