写在前面:各位看到此博客的小伙伴,如有不对的地方请及时通过私信我或者评论此博客的方式指出,以免误人子弟。多谢!
记录一下使用并行流、CompletableFuture、及普通处理方式的效率问题,创建一个taskList表示要处理的任务列表,新建task方法,sleep两秒,模拟下处理每个任务所需时间,先测试一下使用并行流,直接上代码:
public class ParallelStreamAndCompletableFuture {
static final List<Object> taskList = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
public static void main(String[] args) throws Exception{
long startTime = System.currentTimeMillis();
List<Object> result =
taskList.parallelStream().map(s -> task(s)).collect(Collectors.toList());
System.out.println(result);
long endTime = System.currentTimeMillis();
System.out.println("使用 parallelStream 耗时:" + (endTime - startTime));
}
static public Object task(Object i) {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (final InterruptedException e) {
throw new RuntimeException(e);
}
return i;
}
}
执行结果如下:
可以看到使用并行流处理用时6秒多点,比起挨个处理已经块很多了,并且并行流使用简单,不过从打印上看,并行流内部也是使用forkJojin线程池实现的,前面说到,ForkJoinPool 的线程数默认是 CPU 的核心数。但是,最好不要所有业务共用一个线程池,因为,一旦有任务执行一些很慢的 I/O 操作,就会导致线程池中所有线程都阻塞在 I/O 操作上,从而造成线程饥饿,进而影响整个系统的性能。所以使用并行流的时候还是需要注意一下的。
使用CompletableFuture测试下,代码如下:
public class ParallelStreamAndCompletableFuture {
static final List<Object> taskList = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
public static void main(String[] args) throws Exception{
long startTime = System.currentTimeMillis();
List<CompletableFuture<Object>> futures =
taskList.stream().map(s -> CompletableFuture.supplyAsync(() -> task(s))).collect(Collectors.toList());
List<Object> result = futures.stream().map(s -> s.join()).collect(Collectors.toList());
System.out.println(result);
long endTime = System.currentTimeMillis();
System.out.println("使用 CompletableFuture 耗时:" + (endTime - startTime));
}
static public Object task(Object i) {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (final InterruptedException e) {
throw new RuntimeException(e);
}
return i;
}
}
执行结果如下:
话费的时间比并行流多2秒左右,说明fork join的过程确实也是很消耗时间的,在介绍fork/join的时候也说过,使用ForkJoin时,任务的量一定要大,否则太小,forkjoin拆分合并任务也是需要时间的,对于计算量比较小的任务,拆分合并所花费的时间可能会大于计算时间,效率不一定会高。
CompletableFuture可以指定线程池,指定线程池后代码如下:
public class ParallelStreamAndCompletableFuture {
static final List<Object> taskList = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
static ExecutorService executor = Executors.newFixedThreadPool(10);
public static void main(String[] args) throws Exception{
long startTime = System.currentTimeMillis();
List<CompletableFuture<Object>> futures =
taskList.stream().map(s -> CompletableFuture.supplyAsync(() -> task(s),executor)).collect(Collectors.toList());
List<Object> result = futures.stream().map(s -> s.join()).collect(Collectors.toList());
System.out.println(result);
long endTime = System.currentTimeMillis();
System.out.println("使用 CompletableFuture+线程池 耗时:" + (endTime - startTime));
}
static public Object task(Object i) {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (final InterruptedException e) {
throw new RuntimeException(e);
}
return i;
}
}
执行结果如下:
只用了两秒,当然也是因为我开了10个线程,不过这也是使用CompletableFuture的一个好处吧,可以自定义线程池的大小,如果设置合理,对性能提示还是很大的。
PS:注意真正用的时候不要使用Executors创建线程池。