• Java并发编程—java异步Future的迭代过程


            在我们java多线程中,我想做一件事儿,但是我又不想影响主线程的执行,很多铁子都会想到用异步任务完成,这个时候我们的主角FutureTask就登场了。

    🍏 一、FutureTask介绍

            FutureTask提供了对Future的基本实现,是一个可取消的异步计算,可以调用方法去开始和取消一个计算,可以查询计算是否完成并且获取计算结果。只有当计算完成时才能获取到计算结果,一旦计算完成,计算将不能被重启或者被取消,除非调用runAndReset方法。同时他也实现了Runnable接口,因此FutureTask交由Executor执行,可以直接用线程调用执行;

    🍄 FutureTask类的架构图:

    🍏 二、Future和Callable接口

            先给大家介绍2个新的接口,Future可以理解成是一个规范,FutureTask是实现了它,而Callable是需要用到的参数类型

            🍄 1、Future接口定义了操作异步任务执行一些方法,如获取异步任务的执行结果、取消任务的执行、判断任务是否被取消、判断任务是否执行完毕等。

           🍄  2、Callable接口中定义了需要有返回的任务需要实现的方法。比如主线程让一个子线程去执行任务,子线程可能比较耗时,启动子线程开始执行任务后,主线程就去做其他事情了,过一会儿才去获取子任务的执行结果;

    🍏 三、简单案例

            🍨 1、了解了上面的2个接口后,这里我们去做一个案例,我要创建一个异步任务然后进行异步计算,要求返回一个计算值,且主线程继续执行,不会因为子线程的业务影响到主线程;

    1. //1、创建一个异步任务 传递一个Callable的接口的参数,5秒后返回一个1024
    2. //使用的是函数式编程,这里的参数就是Callable类型的
    3. FutureTask task = new FutureTask<>(() -> {
    4. TimeUnit.SECONDS.sleep(5);
    5. return 1024;
    6. });
    7. //2、创建一个线程,把异步任务丢进去
    8. new Thread(task, "t1").start();
    9. //3、主线程继续执行
    10. System.out.println("程序运行执行业务中...");
    11. //4、获取异步任务里面的返回值
    12. System.out.println(task.get());
    13. //5、结束提醒
    14. System.out.println("运行结束...");

            🍨 2、上面注释说到了这个参数就是Callable类型的,这个可以从FutureTask的构造方法得到,所以这也是上面为啥要先介绍一下的原因:

    1. public FutureTask(Callable callable) {
    2. if (callable == null)
    3. throw new NullPointerException();
    4. this.callable = callable;
    5. this.state = NEW; // ensure visibility of callable
    6. }

           🍨 3、上面的案例我们看一下效果,在控制台里面,主程序执行业务输出语句并没有因为子线程的执行而延迟;

            🍨 4、但是,发现了一个问题,“运行结束...”这一句话是在1024输出后才输出的,所以从某种意义上造成了阻塞的现象出现,这和我们说到的高并发就相违背了。在这里的解决办法,就是将子线程的值输出这句代码放到最后进行执行,能治标不治本的解决阻塞问题,代码如下:

    1. public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
    2. //1、创建一个异步任务
    3. FutureTask task = new FutureTask<>(() -> {
    4. TimeUnit.SECONDS.sleep(5);
    5. return 1024;
    6. });
    7. //2、创建一个线程,把异步任务丢进去
    8. new Thread(task, "t1").start();
    9. //3、主线程继续执行
    10. System.out.println("程序运行执行业务中...");
    11. //5、结束提醒
    12. System.out.println("运行结束...");
    13. //4、获取异步任务里面的返回值
    14. System.out.println(task.get());
    15. }

            🍨 5、那么出现了一个新问题,我这个主程序需要异步的这个子任务执行完成后才能结束,举个例子:铁子们在工作中调用其他程序的api,是这样等他无限的执行业务,然后获取值再做自己的业务吗,比如我这里是5s,我就必须等它5s,最终因为工作没完成,或者效率低下,岂不是只有自己背着包包走人了,所以,我们想控制一下这个时间,在规定时间范围内,等他响应该怎么用呢,解决办法如下:

    1. System.out.println(task.get());
    2. //用下面的代码替换上面get()
    3. System.out.println(task.get(2,TimeUnit.SECONDS));

            🍨 6、通过替换,可以发现会报错,这个是因为规定时间内,子线程没返回数据,那肯定不是咱们主线程的问题了;

    综上:在这个简单的案例中,模拟了异步操作一些耗时的业务,同时不影响主业务的进行,相信很多铁子是看懂了怎么使用异步方法,但是也有一些铁子会有疑惑,在高并发中不允许有阻塞的,那么这个阻塞该怎么解决呢。。。

    🍏 四、阻塞的替代:

           🚗 给大家传播一个概念,在高并发开发中,是几乎不允许阻塞出现的,如果出现了,请用轮询去代替这个问题;

            在上面的案例中,用轮询代替阻塞现象的解决办法如下:

    1. public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
    2. //1、创建一个异步任务
    3. FutureTask task = new FutureTask<>(() -> {
    4. TimeUnit.SECONDS.sleep(5);
    5. return 1024;
    6. });
    7. //2、创建一个线程,把异步任务丢进去
    8. new Thread(task, "t1").start();
    9. //3、主线程继续执行
    10. System.out.println("程序运行中...");
    11. //获取异步任务里面的返回值
    12. // System.out.println(task.get());
    13. // System.out.println(task.get(2,TimeUnit.SECONDS));
    14. //轮询获取
    15. while (true) {
    16. TimeUnit.SECONDS.sleep(1);
    17. if (task.isDone()) {
    18. System.out.println(task.get());
    19. break;
    20. } else {
    21. System.out.println("GGG");
    22. }
    23. }
    24. //结束提醒
    25. System.out.println("运行结束...");
    26. }

    🚗 在这个案例中,有FutureTask里面的isDone()作为轮询使用,能勉强解决一点点问题,但是,这还不是我们想要的一个效果

    🍕 比如我还想完成一些复杂的任务:

    1、应对异步任务的完成时间,我希望它完成了可以告诉我,也就是我们常说的回调通知

    2、将2个或者多个异步计算合成一个异步计算,这几个异步计算相互独立,同时第二个又依赖于第一个的结果

    3、当Future集合中某个任务最快结束时,返回结果。

    ......

    可想而知,在这个FutureTask里面就显得吃力了,那么该用一个什么样的方式去解决Future带给大家的困扰呢,也就是对Future的一个改进,我们该如何去改进,这个问题就留到下一篇文章给大家分享了,本篇只讲初始的异步应用,想看进阶的应用请移步:

    Java并发编程—CompletableFuture的介绍和使用_小魏快起床的博客-CSDN博客

    🍏 六、总结:

    🚗 1、分开的子任务分开执行互不干扰可以用FutureTask执行,get()方法获取子任务的值建议放到最后;

    🚗 2、Future的缺点:会有阻塞的现象出现,主要是调用了get()方法;

    🚗 3、对get()优化是给get()添加等待时间,例如get(2,TimeUnit.SECONDS);

    🚗 4、高并发中出现了阻塞现象请用轮询解决;

    🚗 5、更深的应用场景Future会比较吃力,所以会用到CompletableFuture这个类

  • 相关阅读:
    第二周学习:卷积神经网络
    QQ邮箱怎么设置SMTP接口服务器?
    Java学习笔记(二十)
    pytest-yaml 测试平台-1.新增项目和用例(有平台体验地址)
    leetcode-48.旋转图像
    TiDB 社区智慧合集丨TiDB 相关 SQL 脚本大全
    就业 | 面试签约问题
    23种设计模式(十一)外观模式(阁瑞钛伦特软件-九耶实训)
    22/8/3(板子)树状dp板子+中国剩余定理+求组合数3,4+容斥原理
    qt-C++笔记之清空QVBoxLayout中的QCheckBox
  • 原文地址:https://blog.csdn.net/qq_52545155/article/details/128165850