Java multithreading 11: Asynchronous thread pool CompletableFuture

Introduction

In order to make the program more efficient and let the CPU work with maximum efficiency, we will use asynchronous programming. The first thing that comes to mind is to start a new thread to do a certain job. Going a step further, in order for the new thread to return a value to tell the main thread that things are done, the Future is almost on the scene. However, the method provided by Future is that the main thread actively inquires the new thread. If there is a callback function, it would be cool. Therefore, in order to satisfy some of Future’s regrets, the powerful CompletableFuture came along with Java 8.

Future

Traditional multi-threading makes the program more efficient. After all, it is asynchronous and allows the CPU to work fully, but this is limited to newly opened threads without your main thread having to worry about it. For example, you start a new thread just to calculate 1+...+n and then print the result. Sometimes you need the child thread to return the calculation result, and further calculations are performed in the main thread, you need Future.

Look at the following example, the main thread calculates 2+4+6+8+10; the child thread calculates 1+3+5+7+9; finally, the two parts of the result need to be added in the main thread.

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秒

Take a look at the Future interface, only five methods are relatively simple

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

CompletableFuture

The five ways to see Future above are not very rich. Since our main thread is called main, it should be me as the main thread. I hope that the child thread will take the initiative to notify me when it is done. To this end, Java 8 has brought CompletableFuture, an implementation class of Future. In fact, the most fascinating part of CompletableFuture is not to greatly enrich the functions of Future, but to perfectly combine the new features of Java8 streams.

Realize callback, automatic follow-up operation

Let me talk about the method (one of) that CompletableFuture implements callback in advance: thenAccept()

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

The parameter has a Consumer, which uses the new features of Java 8 and behavior parameterization, that is, the parameter is not necessarily a basic type or class, but can also be a function (behavior) or a method (interface).

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

It is worth mentioning that the task connection pool is not shown in this example, and the program will select a task connection pool ForkJoinPool.commonPool() by default.

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

ForkJoinPool started from JDK7 and is called the branch/merge framework. A task can be recursively divided into many molecular tasks to form different streams for parallel execution, along with powerful work-stealing algorithms. Greatly improve efficiency. Of course, you can also specify the connection pool yourself.

CompletableFuture merge

Java 8 does enrich the implementation of Future. CompletableFuture has many methods for everyone to use, but from the above example, in fact, CompletableFuture can do functions that look like Future. After all, your CompletableFuture is not blocked when you use the get() method. My Future can get the return value by myself and perform some operations manually (although the main method must be very uncomfortable). So the next thing, Future will be very troublesome to do. Assuming that our main method only performs the operation of odd collections plus even collections, the operation of calculating these two collections in advance is asynchronously handed over to two child threads. What do we need to do? That's right, start two threads, and when both threads are finished, we perform the final addition. The problem is, how do you know which child thread ended last? (It looks like you can do a poll, call the isDone() method indefinitely...) The rich CompletableFuture function provides us with a method to wait for the two child threads to end, and then perform the addition operation:

    //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);
    }

Look at an example:

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秒

Here we simulate one sleeping for 1 second and one sleeping for 3 seconds, but the real network request time is uncertain. Isn't it cool? The coolest thing is not the phenomenon, but the above operation has used the concept of Java8 stream.

Two child threads are not enough, so there is an **anyOff()** function, which can withstand multiple CompletableFutures and will wait for all tasks to complete.

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

It is very similar to it. There is a method that ends when the first execution ends, and the subsequent tasks do not wait anymore, which can be regarded as a sufficient condition.

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

On the basis of the above example, increase the OddNumberPlus class time a bit longer:

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秒

Actual combat: Asynchronous statistics on homepage report data

/**
	 * 统计首页报表数据
	 * 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);
	}

This actual combat is entirely for use, the scene is not very good

In fact, the front desk Ajax asynchronous request is parallel, adding this asynchronous and parallel CompletableFuture is nothing more than putting multiple Ajax requests together and then returning.

We must say where we are almost there, which saves request and response time

Summary of commonly used methods of 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() 是非阻塞的,尝试获取结果,不一定能拿得到

For other methods, please refer to the  java8 CompletableFuture entry tutorial to explain all the methods in detail with examples.  

summary

There are actually many CompletableFuture methods, such as runAsync(), which is similar to supplyAsync(), but there is no return value; besides thenApply() you can add a callback function, there is thenApply(); there is also injection runAfterBoth(), runAfterEither(), these are well known. There are many more, you can click on the source code of CompletableFuture to take a closer look. Through CompletableFuture, we can feel the power of Java 8 even more through CompletableFuture. The powerful stream concept, behavior parameterization, efficient parallelism, etc. not only make Java easier to write, but also continuously enrich the entire Java ecosystem. Java has been improving all the time, so it has not been eliminated by the times. We Javaer can also continue our career. Thanks to Java, we can make progress together.

Guess you like

Origin blog.csdn.net/zhaofuqiangmycomm/article/details/113320633