假设我有Stream<Callable<SomeClass>> stream;
。该流正在访问超过一百万个不适合内存的对象。
将此转换为Stream<SomeClass>
的惯用方法是什么,以确保Callable::call
在被传递给非线程安全的消费者之前并行执行.sequential().forEach()
(可能通过调用ExecutionService
或其他一些瓶颈机制)?
即。并行处理流,但顺序传递输出(随机顺序ok,只要它是单线程)。
我知道我可以通过在原始流和消费者之间设置Queue
和 public void closePopup() throws InterruptedException{
Thread.sleep(1000);
Actions action=new Actions(driver);
action.keyDown(Keys.ESCAPE).keyUp(Keys.ESCAPE).build().perform();
来做我想做的事。但这似乎是很多代码,是否有一个神奇的单行?
答案 0 :(得分:1)
您仍然可以使用ExecutorService
进行并行化。像这样:
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);
}
});
您可以按顺序处理生成的Stream<SomeClass>
。
如果您直接在Stream<Future<SomeClass>>
上使用forEach / forEachOrdered,则可以在当前未来完成时直接处理生成的SomeClass
- 对象(与使用invokeAll()
时阻止的对象不同每项任务都已完成)。
如果您想按照可用的确切顺序处理可调用的结果,则必须使用CompletionService
,因为必要的调用,所以Future<SomeClass> f = completionService.take()
不能与单个流操作链一起使用提交callables后ExecutorService
。
修改强>
在流中使用Callable
无法按照上面显示的方式运行,因为每个future.get()
都是通过Callables
一个接一个地提交和请求的。
我发现了一个可能的副作用更重的解决方案,将TaskMapper
划分为固定的并行化块。
我使用类Callables
作为映射函数来提交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;
}
}
并将它们映射到块:
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
}
}
这是流操作链的样子:
TaskMapper
这可以如下工作:Spliterator
每次将4个callats提交给服务并将它们映射到一块未来(没有null
)。这是通过每次映射到null
获得第1,第2和第3个可调用来解决的。例如,Integer
可以由虚拟对象替换。将期货映射到结果的映射函数等待块的每个未来的结果。我在我的示例中使用SomeClass
而不是chunkSize
。当映射当前块中的所有期货结果时,将创建并并行化新块。最后,如果流中的元素数量不能由TaskMapper
(在我的示例中为4)分割,则必须从Spliterator
检索剩余的未来并在流外部处理。 / p>
这个结构适用于我进行的测试,但我知道由于副作用,状态良好和流的未定义评估行为,它可能很脆弱。
<强> EDIT2:强>
我使用自定义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;
}
}
:
pipeParallelized()
此类提供函数(Callable
),它采用Spliterators
流 - 元素并行执行它们,然后输出包含结果的顺序流。允许Splitterator
为有状态。因此,希望此版本不会违反任何流操作约束。这就是ExecutorServiceSpliterator.pipeParallelized(stream);
可以使用的方式(靠近“神奇的oneliner”):
Callables
这一行采用stream
ExecutorServiceSpliterator
的流并行化它们的执行并返回一个包含结果的顺序流(管道发生延迟 - &gt;应该可以处理数百万个callables),可以处理进一步定期进行流操作。
CompletionService
的实施非常基础。它应该主要说明如何在原则上完成。可以优化服务的重新供应和结果的检索。例如,如果允许生成的流无序,则可以使用@Transactional
def update(Subsidiary subsidiary,User user) {
if (subsidiary == null) {
transactionStatus.setRollbackOnly()
notFound()
return
}
if (subsidiary.hasErrors()) {
transactionStatus.setRollbackOnly()
respond subsidiary.errors, view:'edit'
return
}
user.save flush:true
user.addToSubsidiaries(subsidiary)
user.save flush:true
subsidiary.addToPhones(phone)
subsidiary.save flush:true
request.withFormat {
form multipartForm {
flash.message = message(code: 'default.updated.message', args: [message(code: 'subsidiary.label', default: 'Subsidiary'), subsidiary.id])
redirect subsidiary
}
'*'{ respond subsidiary, [status: OK] }
}
}
。
答案 1 :(得分:0)
您正在寻求一种惯用的解决方案。不鼓励在其行为参数中具有副作用的流(在流的javadoc中明确说明)。
因此惯用解决方案基本上是ExecutorService + Futures和一些循环/ forEach()。如果您有一个Stream作为参数,只需将其转换为带有标准收集器的List。
类似的东西:
ExecutorService service = Executors.newFixedThreadPool(5);
service.invokeAll(callables).forEach( doSomething );
// or just
return service.invokeAll(callables);
答案 2 :(得分:0)
第一个例子:
ExecutorService executor = Executors.newWorkStealingPool();
List<Callable<String>> callables = Arrays.asList(
() -> "job1",
() -> "job2",
() -> "job3");
executor.invokeAll(callables).stream().map(future -> {
return future.get();
}).forEach(System.out::println);
第二个例子:
Stream.of("1", "2", "3", "4", "", "5")
.filter(s->s.length() > 0)
.parallel()
.forEachOrdered(System.out::println);
答案 3 :(得分:0)
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);
}
答案 4 :(得分:0)
其他答案都不适用于我。
我最终确定了这样的事情(伪代码):
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);
}
consume(obj)
循环在单个线程中按顺序执行,而各个可调用任务以异步方式通过CompletionService的多个线程。内存消耗受到限制,因为CompletionService
一次只有正在进行的项目,因为有可用的线程。等待执行的Callables急切地从流中实现,但是与每次开始执行时消耗的内存相比,它的影响可以忽略不计(用例可能会有所不同)。