java多线程11: 异步线程池 CompletableFuture

引子

为了让程序更加高效,让CPU最大效率的工作,我们会采用异步编程。首先想到的是开启一个新的线程去做某项工作。再进一步,为了让新线程可以返回一个值,告诉主线程事情做完了,于是乎Future粉墨登场。然而Future提供的方式是主线程主动问询新线程,要是有个回调函数就爽了。所以,为了满足Future的某些遗憾,强大的CompletableFuture随着Java8一起来了。

Future

传统多线程的却让程序更加高效,毕竟是异步,可以让CPU充分工作,但这仅限于新开的线程无需你的主线程再费心了。比如你开启的新线程仅仅是为了计算1+...+n再打印结果。有时候你需要子线程返回计算结果,在主线程中进行进一步计算,就需要Future了。

看下面这个例子,主线程计算2+4+6+8+10;子线程计算1+3+5+7+9;最后需要在主线程中将两部分结果再相加。

public class OddNumber implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        Thread.sleep(3000);
        int result = 1 + 3 + 5 + 7 + 9;
        return result;
    }
}
public class FutureTest {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newCachedThreadPool();
        OddNumber oddNumber = new OddNumber();
        Future<Integer> future = executor.submit(oddNumber);
        long startTime = System.currentTimeMillis();
        int evenNumber = 2 + 4 + 6 + 8 + 10;
        try {
            Thread.sleep(1000);
            System.out.println("0.开始了:"+ (System.currentTimeMillis()-startTime) +"秒");
            int oddNumberResult = future.get();//这时间会被阻塞
            System.out.println("1+2+...+9+10="+(evenNumber+oddNumberResult));
            System.out.println("1.开始了:"+ (System.currentTimeMillis()-startTime) +"秒");
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}
输出结果:
0.开始了:1001秒
1+2+...+9+10=55
1.开始了:3002秒

看一下Future接口,只有五个方法比较简单

//取消任务,如果已经完成或者已经取消,就返回失败
boolean cancel(boolean mayInterruptIfRunning);
//查看任务是否取消
boolean isCancelled();
//查看任务是否完成
boolean isDone();
//刚才用到了,查看结果,任务未完成就一直阻塞
V get() throws InterruptedException, ExecutionException;
//同上,但是加了一个过期时间,防止长时间阻塞,主线程也做不了事情
V get(long timeout, TimeUnit unit)
    throws InterruptedException, ExecutionException, TimeoutException;

CompletableFuture

上面的看到Future的五个方法,不是很丰富,既然我们的主线程叫做main,就应该以我为主,我更希望子线程做完了事情主动通知我。为此,Java8带来了CompletableFuture,一个Future的实现类。其实CompletableFuture最迷人的地方并不是极大丰富了Future的功能,而是完美结合了Java8流的新特性。

实现回调,自动后续操作

提前说一下CompletableFuture实现回调的方法(之一):thenAccept()

    public CompletableFuture<Void> thenAccept(Consumer<? super T> action) {
        return uniAcceptStage(null, action);
    }

参数有个Consumer,用到了Java8新特性,行为参数化,就是参数不一定是基本类型或者类,也可以是函数(行为),或者说一个方法(接口)。

public class OddNumberPlus implements Supplier<Integer> {
    @Override
    public Integer get() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return 1+3+5+7+9;
    }
}
public class CompletableFutureTest {
    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();
        final int evenNumber = 2 + 4 + 6 + 8 + 10;
        CompletableFuture<Integer> oddNumber = CompletableFuture.supplyAsync(new OddNumberPlus());
        try {
            Thread.sleep(1000);
            System.out.println("0.开始了:"+ (System.currentTimeMillis()-startTime) +"秒");
            //看这里,实现回调
            oddNumber.thenAccept(oddNumberResult->
                        {
                            System.out.println("1.开始了:"+ (System.currentTimeMillis()-startTime) +"秒");
                            System.out.println("此时计算结果为:"+(evenNumber+oddNumberResult));
                        });
            oddNumber.get();
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}
输出结果:
0.开始了:1006秒
1.开始了:3006秒
此时计算结果为:55

值得一提的是,本例中并没有显示的创建任务连接池,程序会默认选择一个任务连接池ForkJoinPool.commonPool()

    private static final Executor asyncPool = useCommonPool ?
        ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();

ForkJoinPool始自JDK7,叫做分支/合并框架。可以通过将一个任务递归分成很多分子任务,形成不同的流,进行并行执行,同时还伴随着强大的工作窃取算法。极大的提高效率。当然,你也可以自己指定连接池。

CompletableFuture合并

Java8的确丰富了Future实现,CompletableFuture有很多方法可供大家使用,但是但从上面的例子来看,其实CompletableFuture能做的功能,貌似Future。毕竟你CompletableFuture用get()这个方法的时候还不是阻塞了,我Future蛮可以自己拿到返回值,再手动执行一些操作嘛(虽说这样main方法一定很不爽)。那么接下来的事情,Future做起来就十分麻烦了。假设我们main方法只做奇数合集加上偶数合集这一个操作,提前算这两个合集的操作异步交给两个子线程,我们需要怎么做呢?没错,开启两个线程,等到两个线程都计算结束的时候,我们进行最后的相加,问题在于,你怎么知道那个子线程最后结束的呢?(貌似可以做个轮询,不定的调用isDone()这个方法...)丰富的CompletableFuture功能为我们提供了一个方法,用于等待两个子线程都结束了,再进行相加操作:

    //asyncPool就是上面提到的默认线程池ForkJoinPool
    public <U,V> CompletableFuture<V> thenCombineAsync(
        CompletionStage<? extends U> other,
        BiFunction<? super T,? super U,? extends V> fn) {
        return biApplyStage(asyncPool, other, fn);
    }

看个例子:

public class OddCombine implements Supplier<Integer> {
    @Override
    public Integer get() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return 1+3+5+7+9;
    }
}
public class EvenCombine implements Supplier<Integer> {
    @Override
    public Integer get() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return 2+4+6+8+10;
    }
}

public class CompletableCombineTest {
    public static void main(String[] args) throws Exception{
        CompletableFuture<Integer> oddNumber = CompletableFuture.supplyAsync(new OddCombine());
        CompletableFuture<Integer> evenNumber = CompletableFuture.supplyAsync(new EvenCombine());
        long startTime = System.currentTimeMillis();
        CompletableFuture<Integer> resultFuturn = oddNumber.thenCombine(evenNumber,(odd,even)->{
            return odd + even;
        });
        System.out.println(resultFuturn.get());
        System.out.println("0.开始了:"+ (System.currentTimeMillis()-startTime) +"秒");
    }
}
输出结果:
55
0.开始了:3000秒

这边模拟一个睡1秒,一个睡3秒,但是真正的网络请求时间是不定的。是不是很爽,最爽的还不是现象,而是以上操作已经利用了Java8流的概念。

两个子线程还不够,那么还有**anyOff()**函数,可以承受多个CompletableFuture,会等待所有任务都完成。

    public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs) {
        return andTree(cfs, 0, cfs.length - 1);
    }

与它长的很像的,有个方法,是当第一个执行结束的时候,就结束,后面任务不再等了,可以看作充分条件。

    public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs) {
        return orTree(cfs, 0, cfs.length - 1);
    }

在上面那个例子的基础上,把OddNumberPlus类时间调长一点:

public class OddNumberPlus implements Supplier<Integer> {
    @Override
    public Integer get() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return 1+3+5+7+9;
    }
}
public class CompletableCombineTest {
    public static void main(String[] args) throws Exception{
        CompletableFuture<Integer> oddNumber = CompletableFuture.supplyAsync(new OddCombine());
        CompletableFuture<Integer> evenNumber = CompletableFuture.supplyAsync(new EvenCombine());
        CompletableFuture<Integer> testNumber = CompletableFuture.supplyAsync(new OddNumberPlus());
        long startTime = System.currentTimeMillis();
        CompletableFuture<Object> resultFuturn = CompletableFuture.anyOf(oddNumber,evenNumber,testNumber);
        System.out.println(resultFuturn.get());
        System.out.println("0.开始了:"+ (System.currentTimeMillis()-startTime) +"秒");
    }
}
输出结果:
30
0.开始了:1000秒

实战:异步统计首页报表数据

/**
	 * 统计首页报表数据
	 * zfq
	 */
	@RequiresPermissions("udef:defWorksheet:view")
	@ResponseBody
	@RequestMapping(value = "homeReport")
	public String  findAjaxNum( HttpServletRequest request) {
		Map<String,Object> resultMap=new HashMap<>();
		User user = UserUtils.getUser();
		List<Role> roleList = user.getRoleList();
		Role role=new Role();
		role.setId("");
		roleList.contains(role);
		String formRequest = request.getParameter("homeReportParameter");
		String dsf = defWorksheetService.wkstDataScopeFilter(user.getCurrentUser(), "off", "u");
 		CompletableFuture<List<DefWorksheet>> todayAllNumFuture=null;//今日运维境况
		CompletableFuture<Integer> yearAllNumFuture=null;	//全年工单总量
		CompletableFuture<Integer> yearFinishNumFuture=null;//全年工单完成量
		CompletableFuture<List<DefWorksheet>> groupByMonthFuture=null;//月工单量统计
		CompletableFuture<List<DefWorksheet>> groupByTypeFuture=null;//工单分类统计
		CompletableFuture<List<DefWorksheet>> groupByMonthDayFuture=null;//工单分类统计

		/*
		Integer yearFinishNum=0;
		Integer yearAllNum=0;
		List<DefWorksheet> groupByTypeWorksheetList=new ArrayList<>();
		List<DefWorksheet> groupByMonthDayWorksheetList=new ArrayList<>();
		List<DefWorksheet> groupByMonthWorksheetList=new ArrayList<>();
		List<DefWorksheet> todayAllNumWorksheetList=new ArrayList<>();
		List<CompletableFuture> futureList=new ArrayList<>();*/

		if(formRequest.contains("todayNum")) {
			DefWorksheet defWorksheet=new DefWorksheet();
			defWorksheet.getSqlMap().put("dsf",dsf);
			defWorksheet.setBeginCreateDate(DateUtils.getDayStartTime(new Date()));
			defWorksheet.setEndCreateDate(new Date());
			defWorksheet.setWorksheetState(null);
			todayAllNumFuture = CompletableFuture.supplyAsync(() -> defWorksheetService.findWorksheetStateCount(defWorksheet));
			//futureList.add(todayAllNumFuture);
		}
		if(formRequest.contains("yearAllNum")){
			DefWorksheet defWorksheet=new DefWorksheet();
			defWorksheet.getSqlMap().put("dsf",dsf);
			defWorksheet.setBeginCreateDate(DateUtils.getCurrYearFirst());
			defWorksheet.setEndCreateDate(new Date());
			defWorksheet.setWorksheetState(null);//全年运维工单总量
			yearAllNumFuture = CompletableFuture.supplyAsync(() -> defWorksheetService.findYearCount(defWorksheet));
			//futureList.add(yearAllNumFuture);
			DefWorksheet defFinishWorksheet=new DefWorksheet();
			defFinishWorksheet.getSqlMap().put("dsf",dsf);
			defFinishWorksheet.setBeginCreateDate(DateUtils.getCurrYearFirst());
			defFinishWorksheet.setEndCreateDate(new Date());
			defFinishWorksheet.setWorksheetState(null);//全年运维工单总量
			defFinishWorksheet.setWorksheetState(8);//全年工单完成总量
			yearFinishNumFuture = CompletableFuture.supplyAsync(() -> defWorksheetService.findYearCount(defFinishWorksheet));
			//futureList.add(yearFinishNumFuture);
		}
		if(formRequest.contains("groupByMonthNum")){
			DefWorksheet defWorksheet=new DefWorksheet();
			defWorksheet.getSqlMap().put("dsf",dsf);
			defWorksheet.setBeginCreateDate(DateUtils.getBeforeMonthDate(new Date(),12));
			defWorksheet.setEndCreateDate(new Date());
			defWorksheet.setWorksheetState(null);//全年运维工单总量
			groupByMonthFuture = CompletableFuture.supplyAsync(() -> defWorksheetService.groupByMonthNum(defWorksheet));
			//futureList.add(groupByMonthFuture);
		}
		if(formRequest.contains("groupByWorksheetType")){
			DefWorksheet defWorksheet=new DefWorksheet();
			defWorksheet.getSqlMap().put("dsf",dsf);
			defWorksheet.setBeginCreateDate(DateUtils.getmindate());
			defWorksheet.setEndCreateDate(new Date());
			defWorksheet.setWorksheetState(null);//本月工单分类统计
			groupByTypeFuture = CompletableFuture.supplyAsync(() -> defWorksheetService.groupByWorksheetType(defWorksheet));
			//futureList.add(groupByTypeFuture);
		}
		if(formRequest.contains("groupByMonthDayNum")){
			DefWorksheet defWorksheet=new DefWorksheet();
			defWorksheet.getSqlMap().put("dsf",dsf);
			defWorksheet.setBeginCreateDate(DateUtils.getmindate());
			defWorksheet.setEndCreateDate(new Date());
			defWorksheet.setWorksheetState(null);//全年运维工单总量
			groupByMonthDayFuture= CompletableFuture.supplyAsync(() -> defWorksheetService.groupByMonthDayNum(defWorksheet));
			//futureList.add(groupByMonthDayFuture);
		}
		//CompletableFuture[] allFutureTask = new CompletableFuture[futureList.size()];
		//futureList.toArray(allFutureTask);

		/* if(todayAllNumFuture!=null&&yearAllNumFuture!=null &&yearFinishNumFuture!=null
				&&groupByMonthFuture!=null&& groupByTypeFuture!=null&& groupByMonthDayFuture!=null){*/
		//CompletableFuture.allOf(todayAllNumFuture,yearAllNumFuture,yearFinishNumFuture,groupByMonthFuture,groupByTypeFuture,groupByMonthDayFuture).join();
		//}
		//CompletableFuture.allOf(allFutureTask);

		//CompletableFuture.allOf(yearAllNumFuture).join();

		if(formRequest.contains("todayNum")){
			List<DefWorksheet>  todayAllNumWorksheetList = todayAllNumFuture.join();
			AtomicReference<Integer> todayRegisterNum= new AtomicReference<>(0);
			AtomicReference<Integer> todayHandleNum= new AtomicReference<>(0);
			AtomicReference<Integer>  todayFinishNum=new AtomicReference<>(0);
			AtomicReference<Integer>  todayTobeClaimedNum=new AtomicReference<>(0);
			if(todayAllNumWorksheetList.size()>0){
				resultMap.put("todayAllNum",todayAllNumWorksheetList.size());
				todayAllNumWorksheetList.stream().forEach(defWorksheetD ->{
					if(defWorksheetD.getWorksheetState()==-1){
						todayRegisterNum.updateAndGet(v -> v + 1);
					}else if(defWorksheetD.getWorksheetState()==3){
						todayHandleNum.updateAndGet(v -> v + 1);
					}else if(defWorksheetD.getWorksheetState()==8){
						todayFinishNum.updateAndGet(v -> v + 1);
					}else if(defWorksheetD.getWorksheetState()==2){
						todayTobeClaimedNum.updateAndGet(v -> v + 1);
					}
				});
			}
			resultMap.put("todayRegisterNum",todayRegisterNum);
			resultMap.put("todayHandleNum",todayHandleNum);
			resultMap.put("todayFinishNum",todayFinishNum);
			resultMap.put("todayTobeClaimedNum",todayTobeClaimedNum);
		}
		if(formRequest.contains("yearAllNum")){
			Integer yearAllNum=yearAllNumFuture.join();
			resultMap.put("yearAllNum",yearAllNum);
			Integer yearFinishNum =yearFinishNumFuture.join();
			resultMap.put("yearFinishNum",yearFinishNum);
		}
		if(formRequest.contains("groupByMonthNum")){
			List<DefWorksheet> 	groupByMonthWorksheetList=groupByMonthFuture.join();
			List<Object> resultList=new ArrayList<>();
			List<String>  months=new ArrayList<>();
			List<Integer> integers=new ArrayList<>();
			groupByMonthWorksheetList.stream().forEach(defWorksheetD ->{
				months.add(defWorksheetD.getGroupDate());
				integers.add(defWorksheetD.getCountValue());
			});

			resultList.add(months);
			resultList.add(integers);
			resultMap.put("groupByMonthNum",resultList);
		}
		if(formRequest.contains("groupByWorksheetType")){
			List<Object> resultList=new ArrayList<>();
			List<DefWorksheet> groupByTypeWorksheetList= groupByTypeFuture.join();
			groupByTypeWorksheetList.stream().forEach(defWorksheetD ->{
				Map<String,Object> mapData=new HashMap<>();
				String name="";
				if(StringUtils.isBlank(defWorksheetD.getQsType1())){
					name="未选择";
				}else{
					name=BasicTreeUtils.getBasicTreeLabel(defWorksheetD.getQsType1(),"sys_basicTree_gong_dan_fen_lei","");
				}
				mapData.put("name",name);
				mapData.put("value",defWorksheetD.getCountValue());
				resultList.add(mapData);
			});
			resultMap.put("groupByWorksheetType",resultList);
		}
		if(formRequest.contains("groupByMonthDayNum")){
			List<DefWorksheet> 	groupByMonthDayWorksheetList=groupByMonthDayFuture.join();
			List<Object> resultList=new ArrayList<>();
			List<String>  months=new ArrayList<>();
			List<Integer> integers=new ArrayList<>();
			groupByMonthDayWorksheetList.stream().forEach(defWorksheetD ->{
				String substringDate = defWorksheetD.getGroupDate().substring(defWorksheetD.getGroupDate().length() - 2);
				System.out.println(substringDate);
				months.add(substringDate);
				integers.add(defWorksheetD.getCountValue());
			});
			resultList.add(months);
			resultList.add(integers);
			resultMap.put("groupByMonthDayNum",resultList);
		}
		return JsonMapper.toJsonString(resultMap);
	}

本此实战完全是为了用而用,场景并不太好

其实前台ajax异步请求就是并行的,增加这个 异步并行的 CompletableFuture无非就就是把多个ajax请求放到一起再返回,

一定要说快到哪了,就是节省了请求和响应时间

CompletableFuture常用的方法总结


//比较特殊,他入参就是返回值,也就是说他可以用来执行需要其他返回值的异步任务。
public static <U> CompletableFuture<U> completedFuture(U value)
 
//无返回值,使用默认线程池
public static CompletableFuture<Void> 	runAsync(Runnable runnable)
 
//无返回值,使用自定义线程池
public static CompletableFuture<Void> 	runAsync(Runnable runnable, Executor executor)
 
//有返回值,使用默认线程池
public static <U> CompletableFuture<U> 	supplyAsync(Supplier<U> supplier)
 
//有返回值,使用自定义线程池
public static <U> CompletableFuture<U> 	supplyAsync(Supplier<U> supplier, Executor executor)


//等待全部任务都结束,线程任务才全部结束
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs

//任何一个任务结束,线程任务全部结束
public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs) 


join()和get() 和getNow(T valueIfAbsent)  都是获取返回结果调用

1.join()方法抛出的是uncheck异常(即未经检查的异常),不会强制开发者抛出,
会将异常包装成CompletionException异常 /CancellationException异常,但是本质原因还是代码内存在的真正的异常,
2.get()方法抛出的是经过检查的异常,ExecutionException, InterruptedException 需要用户手动处理(抛出或者 try catch,
3.join() 和get()阻塞获取结果 都会一定拿到返回结果,除非异常
    
  getNow() 是非阻塞的,尝试获取结果,不一定能拿得到

其它方法参考  java8 CompletableFuture入门 使用教程 详解所有方法 附实例  

小结

CompletableFuture的方法其实还有很多,常用的比如说runAsync(),类似于supplyAsync(),只是没有返回值;除了thenApply()可以加回调函数以外,还有thenApply();还有注入runAfterBoth()、runAfterEither(),这些见名知意。还有很多,可以点开CompletableFuture这个类的源码仔细看一看。见微知著,透过CompletableFuture,更加感觉到Java8的强大,强大的流概念、行为参数化、高效的并行理念等等,不仅让Java写起来更爽,还不断丰富Java整个生态。Java一直在进步,所以没有被时代淘汰,我们Javaer也可以继续职业生涯,感谢Java,一起进步。

猜你喜欢

转载自blog.csdn.net/zhaofuqiangmycomm/article/details/113320633