多线程异步回调技术

一、任务代码

1.1 需要执行的任务类

@Component
public class Task {

    public String doTaskOne() throws Exception {
        long start = System.currentTimeMillis();
        Thread.sleep(1000);
        long end = System.currentTimeMillis();
        System.out.println("任务一耗时:" + (end - start) + "毫秒");
        return "任务一的执行结果";
    }

    public String doTaskTwo() throws InterruptedException {
        long start = System.currentTimeMillis();
        Thread.sleep(2000);
        long end = System.currentTimeMillis();
        System.out.println("任务二耗时:" + (end - start) + "毫秒");
        return "任务二的执行结果";
    }

    public String doTaskThree() throws InterruptedException {
        long start = System.currentTimeMillis();
        Thread.sleep(3000);
        long end = System.currentTimeMillis();
        System.out.println("任务三耗时:" + (end - start) + "毫秒");
        return "任务三的执行结果";
    }

    public String doTaskFour() throws InterruptedException {
        long start = System.currentTimeMillis();
        Thread.sleep(4000);
        long end = System.currentTimeMillis();
        System.out.println("任务四耗时:" + (end - start) + "毫秒");
        return "任务四的执行结果";
    }
}

1.2 执行任务的业务类

@Service
public class TaskServiceImpl implements ITaskService {

    @Autowired
    private Task task;
    
    public Map<String, Object> doTaskMethod() throws Exception { 
        //执行任务
        String taskOneResult = task.doTaskOne();
        String taskTwoResult = task.doTaskTwo();
        String taskThreeResult = task.doTaskThree();
        String taskFourResult = task.doTaskFour();
        //返回结果集
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("taskOneResult", taskOneResult);
        resultMap.put("taskTwoResult", taskTwoResult);
        resultMap.put("taskThreeResult", taskThreeResult);
        resultMap.put("taskFourResult", taskFourResult);
        return resultMap;
    }
}

1.3 测试入口(基于SpringBoot)

@SpringBootTest(classes = {Application.class})
public class TestThread {

    @Autowired
    private TaskServiceImpl taskService;

    @Test
    public void test() Exception {
        long start = System.currentTimeMillis();
        Map<String, Object> result = taskService.doTaskMethod();
        long end = System.currentTimeMillis();
        System.out.println("任务总耗时:" + (end - start) + "毫秒");
        System.out.println("执行结果为:");
        System.out.println(result);
    }

}

测试入口的代码五轮使用哪种方式都无需改造。

执行结果如下:

java 异步线程lambda Java 异步线程成功回调_java 异步线程lambda

发现执行四个任务需要的时间为10秒钟,因为所有任务都是依次执行。

平常业务开发中可能经常遇见这种情况,导致查询接口执行特别慢。

我们可以使用多线程异步回调的技术来优化代码,在无法优化sql语句时提升查询性能。

二、原生Java方式

2.1 使用jdk8新增的CompletableFuture类来优化上面的代码

@Service
public class TaskServiceImpl {

    @Autowired
    private Task task;

    public Map<String, Object> doTaskMethod() throws Exception {
        //创建四个CompletableFuture对象,并把四个多线程任务作为参数传递进去
        CompletableFuture<String> completableFutureOne = CompletableFuture.supplyAsync(
        () -> {
            String taskResult = null;
            try {
                taskResult = task.doTaskOne();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return taskResult;
        });

        CompletableFuture<String> completableFutureTwo = CompletableFuture.supplyAsync(
        () -> {
            String taskResult = null;
            try {
                taskResult = task.doTaskTwo();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return taskResult;
        });

        CompletableFuture<String> completableFutureThree = CompletableFuture.supplyAsync(
        () -> {
            String taskResult = null;
            try {
                taskResult = task.doTaskThree();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return taskResult;
        });

        CompletableFuture<String> completableFutureFour = CompletableFuture.supplyAsync(
        () -> {
            String taskResult = null;
            try {
                taskResult = task.doTaskFour();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return taskResult;
        });

        //依次获得任务的执行结果  并返回结果集
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("taskOneResult", CompletableFutureOne.get());
        resultMap.put("taskTwoResult", CompletableFutureTwo.get());
        resultMap.put("taskThreeResult", CompletableFutureThree.get());
        resultMap.put("taskFourResult", CompletableFutureFour.get());

        //如果不考虑获取各任务返回结果,只需要四个异步任务执行完毕即可的话。可以使用如下代码
        //join()必须要写,它可以让线程阻塞在这,等待四个任务都执行完毕
        //要不然任务都没执行完就return resultMap了
        //CompletableFuture.allOf(task1,task2,task3,task4).join();
        
        return resultMap;
    }

}
api解释:

1) 关于lambda表达式创建多线程的解释

创建一个多线程可以使用如下lambda表达式的方式:

Runnable runnable = () -> System.out.println("任务的执行结果");

这个表达式就相当于创建了一个Runnable接口的实现类。->右侧就是run方法的内容。

2) 关于CompletableFuture的解释

字面意思就是可完成的Future。主要是为了解决jdk8之前的Future类带来的弊端。

3) 关于CompletableFuture.supplyAsync()的解释

supplyAsync方法用于创建一个CompletableFuture对象,它的参数接收一个Supplier函数式接口。

该方法创建的CompletableFuture对象会异步执行当前传入的计算任务。

在调用端,可以通过get或join获取任务执行的结果。

4) 关于CompletableFutureOne.get()的解释

可以获取CompletableFuture对象中异步任务的执行结果。

5) 关于CompletableFutureOne.allOf()的解释

异步执行参数里面的异步任务。

6)join()的作用

它可以让线程阻塞在这,等待allOf中的任务都执行完毕

2.2 执行结果

java 异步线程lambda Java 异步线程成功回调_spring boot_02

使用多线程加上CompletableFuture优化后,任务的执行的总时间几乎和任务四的执行时间一致。

大大缩短了任务执行的时间。

同时也能依次获取到异步任务的结果。可以说是非常厉害了。

但是发现上面的代码还是比较的冗余。有没有办法再优化一下。

二、SpringBoot+@Async方式

2.1 开启@Async支持

SpringBoot启动类上加@EnableAsync注解开启@Async支持

@SpringBootApplication
@EnableAsync
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

2.2 改造Task类

@Component
public class Task {

    @Async
    public CompletableFuture doTaskOne() throws InterruptedException {
        long start = System.currentTimeMillis();
        Thread.sleep(1000);
        long end = System.currentTimeMillis();
        System.out.println("任务一耗时:" + (end - start) + "毫秒");
        return CompletableFuture.completedFuture("任务一的执行结果");
    }

    @Async
    public CompletableFuture doTaskTwo() throws InterruptedException {
        long start = System.currentTimeMillis();
        Thread.sleep(2000);
        long end = System.currentTimeMillis();
        System.out.println("任务二耗时:" + (end - start) + "毫秒");
        return CompletableFuture.completedFuture("任务二的执行结果");
    }

    @Async
    public CompletableFuture doTaskThree() throws InterruptedException {
        long start = System.currentTimeMillis();
        Thread.sleep(3000);
        long end = System.currentTimeMillis();
        System.out.println("任务三耗时:" + (end - start) + "毫秒");
        return CompletableFuture.completedFuture("任务三的执行结果");
    }

    @Async
    public CompletableFuture doTaskFour() throws InterruptedException {
        long start = System.currentTimeMillis();
        Thread.sleep(4000);
        long end = System.currentTimeMillis();
        System.out.println("任务四耗时:" + (end - start) + "毫秒");
        return CompletableFuture.completedFuture("任务四的执行结果");
    }
}
api解释:

1) 关于@Async

@Async注解能将原来的同步方法变为异步方法。

2) 关于CompletableFuture.completedFuture的解释

completedFuture方法是一个静态方法,作用也是创建一个CompletableFuture对象。

源码如下:

public static <U> CompletableFuture<U> completedFuture(U value) {
return new CompletableFuture<U>((value == null) ? NIL : value);
}

2.3 改造TaskServiceImpl

@Service
public class TaskServiceImpl {

    @Autowired
    private Task task;

    public Map<String, Object> doTaskMethod() throws Exception {
        //执行任务
        CompletableFuture completableFutureOne = task.doTaskOne();
        CompletableFuture completableFutureTwo = task.doTaskTwo();
        CompletableFuture completableFutureThree = task.doTaskThree();
        CompletableFuture completableFutureFour = task.doTaskFour();

        //返回结果集
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("taskOneResult", completableFutureOne.get());
        resultMap.put("taskTwoResult", completableFutureTwo.get());
        resultMap.put("taskThreeResult", completableFutureThree.get());
        resultMap.put("taskFourResult", completableFutureFour.get());

        return resultMap;
    }
}

此时doTaskMethod中就已经清爽很多了。

2.4 执行结果

java 异步线程lambda Java 异步线程成功回调_spring_03

发现依旧保持了异步任务的高性能,又让代码更加简洁。还能获取到异步任务的结果。

但是大量使用这种异步任务,对服务器的要求是比较高的。

所以开发中并不需要把所有任务都写成这个样子。

只需要对和上面这些情况类似的情况,就可以使用异步回调的方式来优化。