• Java基础浅聊Future带来的异步优点和缺点


    本篇聊一下几个Future接口,其实在前面聊callable实现线程的时候,用到过一个实现类FutureTask,其就是实现了Future接口。

    现在看一下官网堆Future的解释:

    在这里插入图片描述

    可以看出Future定义了一个操作异步任务的一些方法,如获取异步任务的结果,取消任务的执行,判断任务是否被取消,判断任务是否执行完毕等等操作。

    其实本质就是主线程在做事情的时候,可以把一个需要计算,因为其消耗时间,不能也不需要立即返回结果,而主线程可以将任务扔给子线程。主线程继续做自己要做的事情,过一会获取子线程执行的结果或者变更其任务状态。

    其实如果从有JavaScript的话可能对于异步调用更好理解,因为JavaScript本身就是单线程,其很多事务需要从通过异步回调来完成。比如读写等操作等

    Java可以支持多线程,为什么又要有异步。其实有些明白一个基础概念,那就是异步操作不等于多线程

    因为异步处理不用阻塞当前线程等待处理完成,而是允许后续操作,直至其它线程处理完成,并回调通知此线程。不过不是所有的异步的都需要回调,比如读写中的读可以返回结果,写就没有必须要的。

    其实异步对应的是同步,是这两个概念的对立,而多线程却又并行和并发两个状态,而其对应的是单线程。

    所以对应关系是:异步 VS 同步 , 多线程 VS 单线程。

    • 异步 VS 同步
      • 同步:所有的线程或者单个线程都同时在做一件事情。用一个不恰当的比喻:同步就像最初的快递,快递小哥给你打电话得等你本人来后亲自签收。你说需要等十分才行,这个时候小哥就需要一直等着你,才可以送下一单。
      • 异步: 对于某个需求不需要以及回应,主线程继续允许,需要的时候回调通过异步计算的结果即可快递站点的模式,快递到后快递小哥给你发个短信告知你到哪取、取件码多少,收到短信后按短信内容去相应站点取你的快递就行了。这样快递小哥不需要等你了,至于后面你取快递的过程不会影响快递小哥送快递时间。

    简单的说就是先把请求承接下来,放入存储容器中,在从容器中把请求取出,统一调度处理。

    现象还是使用futureTask这个实现类来实现一下异步操作,其实这个为什么会这样写,可以看下另一篇文章.

    public class test {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
         FutureTask<Integer> futureTask=new FutureTask<Integer>(new testThread());
    //     FutureTask实现 runable接口和future接口
         new Thread(futureTask).start();
         System.out.println("main线程继续执行");
        
            System.out.println("异步回调:"+futureTask.get());
        }
    }
    class testThread implements Callable {
    
    
        @Override
        public Object call() throws Exception {
        
            return 1024;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在这里插入图片描述

    前面说Callable说其与Runable接口就是其有返回数据,当然这个也是异步的一个特点,如果没有回调结果,那么异步再很多时候都没有意义了。其实Runnable默认也是异步的,要不然在通过多线程处理某一个数据, 通过 synchronized关键字、Lock等来保证数据的安全。

    不过既然Runable可以实现多线程,而这种也可以实现异步,不过虽然可以完成异步操作但是有一个弊端那就是无法返回异步执行的结果,所以就需要一个异步多线程任务执行且有返回有结果,所有就有三个特点:多线程,有返回,异步任务。

    证明Runable是异步:

    public class test {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            long start1= System.currentTimeMillis();
     
            Runnable R1=()->{
                try {
    
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            };
            Runnable R2=()->{
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            };
            new Thread(R1).start();
            new Thread(R2).start();
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            long end1= System.currentTimeMillis();
            System.out.println("运行时间毫秒:"+(end1-start1));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    在这里插入图片描述

    可以看出Runable实现的多线程也是异步的,这个时候可以看出其中一个线程的时间超过5秒,也不会影响主线程。不过其有一个问题,那就是如果是不需要返回的话没有问题,但是如果有返回值呢?所以就需要Callable这接口了,不过这可以看下另一篇文章.所以需要Callable和FutureTask两个搭配使用与多线程关联起来。这个可以看下Callable的call方法:

         @Override
    //  可以看出一点,那就是其可以抛出异常,以及有返回值
         public Object call() throws Exception {
     
             return null;
         }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    再来一个FutureTask和Callable,演示异步多线程:

    ublic class test {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            long start1= System.currentTimeMillis();
    //        为了演示不使用lambda表达式
            FutureTask F1=new FutureTask(
                    new Callable() {
                        @Override
                        public Object call() throws Exception {
                            try {
    
                                TimeUnit.SECONDS.sleep(2);
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                            return "tast1 over";
                        }
                    }
            );
            FutureTask F2=new FutureTask(()->{
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                return "tast2 over";
            });
            new Thread(F1).start();
            new Thread(F2).start();
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            long end1= System.currentTimeMillis();
            System.out.println("运行时间毫秒:"+(end1-start1));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    在这里插入图片描述

    不过可以通过线程池演示,这里就不再演示了不然就会重复,执行下面演示时候使用线程池。

    等带异步返回值会产生阻塞

    前面例子演示了异步操作会减少运行的时间,但是其有一个问题,那就是其中一个异步运行的时间明显超过主线程了,但是如果要是要得到异步返回值会发生什么事情呢?

    blic class test {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            long start1= System.currentTimeMillis();
            ExecutorService ES= Executors.newFixedThreadPool(3);
    //        为了演示不使用lambda表达式
            FutureTask F1=new FutureTask(
                    new Callable() {
                        @Override
                        public Object call() throws Exception {
                            try {
    
                                TimeUnit.SECONDS.sleep(2);
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                            return "tast1 over";
                        }
                    }
            );
            FutureTask F2=new FutureTask(()->{
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                return "tast2 over";
            });
            ES.submit(F1);
            ES.submit(F2);
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(F2.get());
            long end1= System.currentTimeMillis();
            System.out.println("运行时间毫秒:"+(end1-start1));
            ES.shutdown();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    在这里插入图片描述

    其实可以看出一点,那就是如果需要得到异步的返回值,如果时间过长就会阻塞主线程的运行。

    在这里插入图片描述

    当然也可以看出get的方法,其设定一个等待的时间。不过无论如何操作其都是会影响却这个异步结果的线程。

    但是是否在使用过程中直接调用嗯,也不是前面一直说就是其可以判断其异步运行的状态 ,如下演示:

    public class test {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
    
            FutureTask F1 = new FutureTask(() -> {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                return "tast1 over";
            });
            new Thread(F1).start();
            while (true) {
                if (!F1.isDone()) {
                    System.out.println("等等现在异步线程还没返回数据");
                    //            这里可以写执行其再等待异步结果的时操作其它发操作
                } else {
                    System.out.println("返回数据" + F1.get());
                    break;
                }
    
            }
            System.out.println("主线程 main");
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    在这里插入图片描述

    所以一般通过循环的方式然后判断状态(isDone())然后返回数据,这样尽量使获得异步结果的不影响阻塞的线程。不过这个有一个问题,那就是会消耗CPU的资源,毕竟其不一定可以得到及时地得到计算结果。

    所以可以看出单纯的使用Future虽然可以得到返回值,但是其获取不是很友好,以及智能公告阻塞或者循环的方式得到任务结果。
    其实这个了解原理,以及会使用即可,既然有不足,那么Java自然也会优化出现新的类来实现异步,后面具继续聊。

    体验异步的优势

    首先来一个例子,那就是比如买NS中的塞尔达卡,对比一下国内几个电商的价格:

    这个可以通过Stream特性来一个例子,因为不使用Stream的话,有些效果无法体现出来。

    非异步模式

    非异步的体验

    public class test {
        public static void main(String[] args) {
            List<NetShop> shoplist = Arrays.asList(
                    new NetShop("拼多多"),
                    new NetShop("京东"),
                    new NetShop("淘宝")
            );
            long start=System.currentTimeMillis();
            List<String> pricelist= shoplist.stream().map((shop)->{
                try {
                    return   String.format("塞尔达在"+"在%s中的价格是%s",shop.getName(),shop.getPrice());
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }).collect(Collectors.toList());
            long end=System.currentTimeMillis();
            pricelist.forEach(price->{
                System.out.println(price);;
            });
            System.out.println("运行的时间:"+(end-start));
        }
    
    
    }
    
    class NetShop {
        String name;
    
        public NetShop(String name) {
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    //    直接通过方法得到一个随机价格,而不在写价格属性了
    
        public Double getPrice() throws InterruptedException {
            TimeUnit.SECONDS.sleep(1);
            Double price = ThreadLocalRandom.current().nextDouble() + 299;
            return price;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    在这里插入图片描述

    异步体验

    public class test {
        public static void main(String[] args) throws Exception {
            List<NetShop> shoplist = Arrays.asList(
                    new NetShop("拼多多"),
                    new NetShop("京东"),
                    new NetShop("淘宝")
            );
            long start = System.currentTimeMillis();
         
                List<String> pricelist = shoplist.stream().map((shop) ->
                        {
                            FutureTask TF = new FutureTask<>(() ->
                                    String.format("塞尔达在" + "在%s中的价格是%s", shop.getName(), shop.getPrice())
                            );
                            new Thread(TF).start();
                            return TF;
    
                        }
    //                CompletableFuture.supplyAsync(() ->
    //
    //                )
                ).collect(Collectors.toList()).stream().map(cp -> {
                    try {
                        return (String) cp.get();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    } catch (ExecutionException e) {
                        throw new RuntimeException(e);
                    }
    
                }).collect(Collectors.toList());
                pricelist.forEach(price -> {
                    System.out.println(price);
                    ;
                });
    
    
    
            long end = System.currentTimeMillis();
    
            System.out.println("运行的时间:" + (end - start));
        }
    
    
    }
    
    class NetShop {
        String name;
    
        public NetShop(String name) {
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    //    直接通过方法得到一个随机价格,而不在写价格属性了
    
        public Double getPrice() {
            Double price = 0d;
            try {
                TimeUnit.SECONDS.sleep(1);
                price = ThreadLocalRandom.current().nextDouble() + 299;
    
            } catch (Exception E) {
                E.printStackTrace();
            } finally {
                return price;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75

    在这里插入图片描述
    明显可以看出时间用了三分之一,可见异步还是有效果了,毕竟是通过多线程实现异步的。

    补充一个疑问,这个疑问目前也没有搞懂原因

    就是stream通过map返回一个stream,然后再直接用map调用异步不会生效是3秒的,但是如果改为:

    //异步没有生效不知道为什么,这个可能需要再研究一下
    map().map()
    
    // 异步生效了
    map().collect(Collectors.toList().map()
    
    • 1
    • 2
    • 3
    • 4
    • 5

    不知道为什么,目前只能通过主页方法体现,以后有机会查询资料再解答吧

  • 相关阅读:
    does not export com.sun.tools.javac.util to unnamed moudle
    【yosys】基础的综合操作(更新中)
    香橙派Zero3安装miniconda3(问题多多,已全部解决)
    ImageIO的应用 (AWT和Swing初接触)
    VEGAS Pro 20发布-剪辑师们,是时候更换了吗?
    论文研读:ViT-V-Net—用于无监督3D医学图像配准的Vision Transformer
    拿下!这些证书可以帮你职场晋升!(PMP/CSPM/NPDP)
    解决你的R语言乱码问题
    设计模式-职责链模式
    红日靶场五(vulnstack5)渗透分析
  • 原文地址:https://blog.csdn.net/u011863822/article/details/126745023