一、java对流处理的封装非常便捷,一般情况一下,因为steam数据都是比较大,或者有规律的数据,这时候利用java便利的接口很容易实现
Stream和parallelStream选择
在从stream和parallelStream方法中进行选择时,我们可以考虑以下几个问题:
1.是否需要并行?
2.任务之间是否是独立的?是否会引起任何竞态条件?
3.结果是否取决于任务的调用顺序?
对于问题1,在回答这个问题之前,需要明确要解决的问题是什么,数据量有多大,计算的特点是什么?并不是所有的问题都适合使用并发程序来求解,比如当数据量不大时,顺序执行往往比并行执行更快。毕竟,准备线程池和其它相关资源也是需要时间的。但是,当任务涉及到I/O操作并且任务之间不互相依赖时,那么并行化就是一个不错的选择。通常而言,将这类程序并行化之后,执行速度会提升好几个等级。
对于问题2,如果任务之间是独立的,并且代码中不涉及到对同一个对象的某个状态或者某个变量的更新操作,那么就表明代码是可以被并行化的。
对于问题3,由于在并行环境中任务的执行顺序是不确定的,因此对于依赖于顺序的任务而言,并行化也许不能给出正确的结果。
二、java的并发处理多任务提供的类CompletableFuture,一是提高实现的功能代码性能,充分利用多核cpu的优势
CompletableFuture的优点是:
常用方法
依赖关系
thenApply():把前面任务的执行结果,交给后面的Function
thenCompose():用来连接两个有依赖关系的任务,结果由第二个任务返回
and集合关系
thenCombine():合并任务,有返回值
thenAccepetBoth():两个任务执行完成后,将结果交给thenAccepetBoth处理,无返回值
runAfterBoth():两个任务都执行完成后,执行下一步操作(Runnable类型任务)
or聚合关系
applyToEither():两个任务哪个执行的快,就使用哪一个结果,有返回值
acceptEither():两个任务哪个执行的快,就消费哪一个结果,无返回值
runAfterEither():任意一个任务执行完成,进行下一步操作(Runnable类型任务)
并行执行
allOf():当所有给定的 CompletableFuture 完成时,返回一个新的 CompletableFuture
anyOf():当任何一个给定的CompletablFuture完成时,返回一个新的CompletableFuture
结果处理
whenComplete:当任务完成时,将使用结果(或 null)和此阶段的异常(或 null如果没有)执行给定操作
exceptionally:返回一个新的CompletableFuture,当前面的CompletableFuture完成时,它也完成,当它异常完成时,给定函数的异常触发这个CompletableFuture的完成
-----------------------------------
java默认线程池大小 java completefuture 默认线程池
三、对比一下parallelStream和CompletableFuture
class MyTask {
private final int number;
private final int duration;
public MyTask(int number, int duration) {
this.number = number;
this.duration = duration;
}
public int calculate() {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(duration * 1000);
} catch (final InterruptedException e) {
throw new RuntimeException(e);
}
return number;
}
@Override
public String toString() {
return "MyTask{" +
"number=" + number +
", duration=" + duration +
'}';
}
}
@Test
public void testParallel() {
List taskList = IntStream.range(0, 10)
.mapToObj(i -> new MyTask(i, 1))
.collect(toList());
StopWatch stopWatch = new StopWatch();
stopWatch.start();
List result = taskList.parallelStream()
.map(MyTask::calculate)
.collect(toList());
System.out.printf("Processed %d tasks in %d millis\n", taskList.size(), stopWatch.getTime());
System.out.println(result);
}
@Test
public void testCompletableFutureSupplyAsync() {
List taskList = IntStream.range(0, 10)
.mapToObj(i -> new MyTask(i, 1))
.collect(toList());
StopWatch stopWatch = new StopWatch();
stopWatch.start();
List result = taskList.stream()
.map(item -> CompletableFuture.supplyAsync(item::calculate))
.collect(toList()).stream().map(CompletableFuture::join)
.collect(toList());
//main线程
System.out.printf("Processed task in %d millis\n", stopWatch.getTime());
System.out.println(result);
}
测试结果:
10个任务总共耗时2015ms,parallelStream用的线程池是ForkJoinPool.commonPool(),从运行结果可以看出,用了7个ForkJoinPool线程和1个主线程。
CompletableFuture耗时2014ms,从耗时上看没有什么特殊的优势,但这一次用了7个ForkJoinPool线程(其中1、2、3号线程被复用),与parallelStream不同的是,主线程没有被用到。
CompletableFuture更加的灵活,我们可以配置其线程池的大小确保整体的计算不会因为等待I/O而发生阻塞,《Java并发编程实战》一书中给出的建议是:
如果要执行的任务是计算密集型的并且没有IO操作,推荐使用并行流parallelStream,因为实现简单效率也高,其使用的线程池ForkJoinPool.commonPool()设置的线程数默认为CPU数量-1,可最大化利用CPU,CPU密集型任务各个线程都很忙碌(运行状态),没有必要创建比核数更多的线程
如果要执行的任务涉及到网络I/O或磁盘I/O等耗时操作,使用CompletableFuture的灵活性更好,因为大部分线程处于等待状态,需要将它们利用起来,让它们更加忙碌,并且在逻辑中加入异常处理可以更有效的监控是什么原因触发了等待。