likes
comments
collection
share

CompletableFutur多线程编程可以如此顺滑

作者站长头像
站长
· 阅读数 11

CompletableFutur多线程编程可以如此顺滑

做任何开发一旦涉及到异步,设计到对共享变量的操作,我们会想到用异步多线程来解决,一个线程串行执行比较慢,那么多个线程一起执行执行效率就会跟上来。

多线程执行如果对共享变量操作就会涉及到线程之间通信,和线程间共享变量的可见性,由此引发出很多多线程问题。

为了解决多线程问题,比如java为了线程的可见性设计了volatile保证线程间可见以及防止指令重排序,为了保证线程间对共享变量的操作设计了各种锁synchronized,锁又涉及到锁的升级,偏向锁,自旋锁,重量级锁等。这给我们日常只关注开发和业务,在使用多线程的时候需要谨慎再谨慎。

如果涉及到多线程,一般第一想到的方案都是线程池,把任务交给线程池去处理,那么有没有一种工具把线程池也封装好了,有默认的线程池,需要任务异步执行直接让这个工具类异步调用就行。

在Java在1.8版本提供了CompletableFuture来支持异步编程,先来看个栗子🌰,我在项目中需要根据List集合给前端封装成各种不同格式,在组装的的时候每个任务耗费时间比较久,如果让主线程同步执行需要前端等很久才会有结果返回,那么如果异步去执行会不会好点,有多少个任务(方法)启用多少个异步线程去执行。串行执行代码如下:

//分组
List<DisplayGroupsVo> groupList = groupList();
map.put("groupSet", groupList);
//日期表格
List<Map<String, Object>> dateSheetList = dateSheet(groupList);
dateSheetMap.put("dataSource", dateSheetList);
map.put("dateSheet", dateSheetMap);
//事件表格
List<Map<String, Object>> eventSheetList = eventSheet();
Map<String, Object> eventSheetMap = new HashMap<>();
eventSheetMap.put("dataSource", eventSheetList);
map.put("eventSheet", eventSheetMap);
//分组表格
List<Map<String, Object>> groupSheetList = groupSheet(eventSheetList);
groupMap.put("dataSource", groupSheetList);
map.put("groupSheet", groupMap);

那么同步执行的话上述四个步骤是一个线程串行执行的,就是先分组,再组装日期表格,再组装事件表格,再组装分组表格。那么我想让这四步需要四个线程分别执行,然后等四个线程每个方法执行完了,把全部的结果返回给前端用CompletableFuture怎么写呢?如下文:

//分组
CompletableFuture<List<DisplayGroupsVo>> c1 = CompletableFuture.supplyAsync(() -> {
    map.put("groupSet", groupList());
    return groupList();
});
List<DisplayGroupsVo groupList = c1.get();

//日期表格
CompletableFuture<List<DisplayGroupsVo>> c2 = CompletableFuture.runAsync(() -> {
    List<Map<String, Object>> dateSheetList = dateSheet(groupList);
    dateSheetMap.put("dataSource", dateSheetList);
    map.put("dateSheet", dateSheetMap);
});

//事件表格
CompletableFuture<List<Map<String, Object>>> c3 = CompletableFuture.supplyAsync(() -> {
    Map<String, Object> eventSheetMap = new HashMap<>();
    eventSheetMap.put("dataSource", eventSheet());
    map.put("eventSheet", eventSheetMap);
    return eventSheet()
});
List<Map<String, Object>> eventSheetList = c3.get();

//分组表格
CompletableFuture<List<DisplayGroupsVo>> c4= CompletableFuture.runAsync(() -> {
    List<Map<String, Object>> groupSheetList = groupSheet(eventSheetList);
    groupMap.put("dataSource", groupSheetList);
    map.put("groupSheet", groupMap);
});

CompletableFuture.allOf(c1, c2, c3, c4).join();

改成CompletableFuture多线程执行,四个串行的步骤还是分别是c1, c2, c3, c4四个线程去执行,执行完 CompletableFuture.allOf(c1, c2, c3, c4).join() 表示等四个线程执行完了把结果一起返回,你会发现用CompletableFuture工具类你不需要关注任何多线程方面的东西。

  • 不需手工维护线程,没有手工维护线程的工作,任务分配线程的工作不需要我们操心。

  • 代码只关注业务不需要关注多线程的任何操作。

上文前端需要四个任务都执行完再一起返给前端,如果我第三个任务,需要等第一个和第二个任务执行完再执行第三个任务。CompletableFuture也能很好支持的如下:

CompletableFuture<List<Map<String, Object>>> c3 =c1.thenCombine(c2,(other, fn)->{
    Map<String, Object> eventSheetMap = new HashMap<>();
    eventSheetMap.put("dataSource", eventSheet());
    map.put("eventSheet", eventSheetMap);
});
  • 任务c3要等待任务c1和任务c2都完成后才能开始,语义很明确。

我上文用了CompletableFuture提供的supplyAsync()runAsync()allOf() 方法,我们去看看这三个方法是怎么异步执行而我们不需要关注任务多线程操作。

  • runAsync()方法

CompletableFutur多线程编程可以如此顺滑

CompletableFutur多线程编程可以如此顺滑

  • runAsync()Runnable接口的run() 方法没有返回值的,会开启新的线程去执行任务,默认用的线程池是ForkJoinPool,这个线程池默认创建的线程数是CPU的核数可以通过jvm参数设置(JVM option:-Djava.util.concurrent.ForkJoinPool.common.parallelism

  • supplyAsync()方法

CompletableFutur多线程编程可以如此顺滑

  • supplyAsync()Supplier接口的 get()方法是有返回值的,返回值调用get()方法获取。

当然我们有时候会有多个模块都需要使用异步去调用,如果都使用默认的线程池ForkJoinPool,如果其中一个任务I/O操作很慢,会拖慢其他模块的任务,这时候我们希望我们每个模块有自己独立线程池,比如事件分析模块和用户行为分析模块的线程池是分开的,CompletableFutur也提供了方法,只需要在调用runAsync()supplyAsync() 把各自的线程池当参数传入。

CompletableFutur多线程编程可以如此顺滑

CompletableFutur多线程编程可以如此顺滑

  • allOf()方法

见名思意需要等多个CompletableFutur任务全部返回结果,然后把全部返回,多个任务是and的关系,还有个anyOf()方法,是or的关系,只需要等待的任务有一个返回就返回最终结果。

CompletableFuture可以让你异步编程变得相对简单,让你快速的实现多线程异步编程,甚至不用手动创建线程池,开发过程中使用起来很顺手哦。快去试试吧!