• 第七章:(1)Callable 接口


    一、创建线程的多种方式

      四种

      1、JDK1.5 之前,传统的方式有两种

        继承 Thread 类

        实现 Runnable 接口

      2、JDK1.5 之后,新增了两种

        使用 Callable 接口

        通过线程池获取线程

    二、Callable 接口

      目前我们学习了有两种创建线程的方法-一种是通过创建 Thread 类,另一种是通过使用 Runnable 创建线程。但是, Runnable 缺少的一项功能是,当线程终止时(即 run()完成时),我们无法使线程返回结果。为了支持此功能,
    Java 中提供了 Callable 接口。

      Callable 是一个接口,这是一个函数式接口,因此可以用作 lambda 表达式或方法引用的赋值对象。
      

    三、Callable 接口与 Runnable 接口的区别

      两个接口的对比:
      (1)Callable 需要实现 call() 方法,Runnable需要实现 run() 方法;
      (2)Callable 的任务执行后可返回值,Runnable 的任务是不能有返回值;返回值是根据 Callable 的泛型来返回的,没有指定,返回 Object;
      (3)Callable 的 call() 方法可以引发异常,可以抛出异常,Runnable 的 run() 方法不能抛出异常;
      (4)运行 Callable 任务可以拿到一个 Future 对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。

    四、Callable 如何使用

      1、Thread 类的构造器

       

      可以发现里面并没有可以传入 Callable 接口的方法。
      所以,不能用 Callable 替代 Runnable。

      2、认识不同的人找中间人

      Java中多态的思想,一个类可以实现多个接口!
      
      可以发现,Runnable 下面有一个实现类 FutureTask,并且该类的构造方法可以传入 Callable 接口。

      

      3、FutureTask 的方法

      

      运行成功后如何获取返回值?
      
      提供了 get() 方法,用于获取返回值。

    五、Future 接口

      当 call() 方法完成时,结果必须存储在主线程已知的对象中,以便主线程可以知道该线程返回的结果。为此,可以使用 Future 对象。

      将 Future 视为保存结果的对象—它可能暂时不保存结果,但将来会保存(一旦Callable 返回)。 Future 基本上是主线程可以跟踪进度以及其他线程的结果的一种方式。要实现此接口,必须重写 5 种方法,这里列出了重要的方法,如下:

    public boolean cancel(boolean mayInterrupt): 用于停止任务。
    如果尚未启动,它将停止任务。如果已启动,则仅在 mayInterrupt 为 true 时才会中断任务。 
    
    public Object get()抛出 InterruptedException, ExecutionException:用于获取任务的结果。
    如果任务完成,它将立即返回结果,否则将等待任务完成,然后返回结果。
    
    public boolean isDone(): 如果任务完成,则返回 true,否则返回 false
    

      

      可以看到 Callable 和 Future 做两件事-Callable 与 Runnable 类似,因为它封装了要在另一个线程上运行的任务,而 Future 用于存储从另一个线程获得的结果。实际上, future 也可以与 Runnable 一起使用。

      要创建线程,需要 Runnable。为了获得结果,需要 future。

    六、FutureTask 概述和原理

      1、FutureTask 是什么

      Java 库具有具体的 FutureTask 类型,该类型实现 Runnable 和 Future,并方便地将两种功能组合在一起。 可以通过为其构造函数提供 Callable 来创建FutureTask。然后,将 FutureTask 对象提供给 Thread 的构造函数以创建Thread 对象。因此,间接地使用 Callable 创建线程。

      2、核心原理

      在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给 Future 对象在后台完成。

      (1)当主线程将来需要时,就可以通过 Future 对象获得后台作业的计算结果或者执行状态;

      (2)一般 FutureTask 多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果;

      (3)仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get 方法;

      (4)一旦计算完成,就不能再重新开始或取消计算;

      (5)get 方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常;

      (6)get 只计算一次,因此 get 方法放到最后

      

      3、FutureTask 举例

      (1)老师上课,口渴了,去买票不合适,讲课线程继续。单开启线程找班上班长帮我买水,把水买回来,需要时候直接get。

      (2)4个同学, 1同学 1+2...5   ,  2同学 10+11+12....50, 3同学 60+61+62,  4同学 100+200,第2个同学计算量比较大,FutureTask单开启线程给2同学计算,先汇总 1 3 4 ,最后等2同学计算位完成,统一汇总。

      (3)考试,做会做的题目,最后看不会做的题目;

      4、代码示例

      (1)案例一

    //实现Runnable接口
    class MyThread1 implements Runnable {
        @Override
        public void run() {
    
        }
    }
    
    //实现Callable接口
    class MyThread2 implements Callable {
    
        @Override
        public Integer call() throws Exception {
            System.out.println(Thread.currentThread().getName()+" come in callable");
            return 200;
        }
    }
    
    public class Demo1 {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            //Runnable接口创建线程
            new Thread(new MyThread1(),"AA").start();
    
            //Callable接口,报错
            // new Thread(new MyThread2(),"BB").start();
    
            //FutureTask
            FutureTask<Integer> futureTask1 = new FutureTask<>(new MyThread2());
    
            //lambda 表达式
            FutureTask<Integer> futureTask2 = new FutureTask<>(()->{
                System.out.println(Thread.currentThread().getName() + " come in callable");
                return 1024;
            });
    
            //创建线程
            new Thread(futureTask2, "AA").start();//调用
            System.out.println(futureTask2.get());
    
            System.out.println(Thread.currentThread().getName() +" come over!");
    
        }
    }

      (2)案例二

    public class Demo2 {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
    
            //lambda 表达式
            FutureTask<Integer> futureTask = new FutureTask<>(()->{
                System.out.println(Thread.currentThread().getName() + " come in callable");
                return 1024;
            });
    
            //创建线程
            new Thread(futureTask, "AA").start();
    
            while (!futureTask.isDone()) {
                System.out.println("wait...");
            }
    
            //调用
            System.out.println(futureTask.get());
    
            //只需要计算一次,第二次直接获取
            System.out.println(futureTask.get());
    
            System.out.println(Thread.currentThread().getName() +" come over!");
    
        }
    }

        整个Future只会执行一次,第二次直接获取运行结果。

      (3)案例三

    public class Demo1 {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
    
            //lambda 表达式
            FutureTask<Integer> futureTask2 = new FutureTask<>(()->{
                System.out.println(Thread.currentThread().getName() + " come in callable");
                return 1024;
            });
    
            FutureTask<Integer> futureTask3 = new FutureTask<>(()->{
                System.out.println(Thread.currentThread().getName() + " come in callable");
                return 2048;
            });
    
            //创建线程
            new Thread(futureTask2, "AA").start();
            new Thread(futureTask3, "BB").start();
    
    
            //调用
            System.out.println(futureTask2.get());
    
            System.out.println(futureTask3.get());
    
            System.out.println(Thread.currentThread().getName() +" come over!");
        }
    }

        如果多个FutureTask线程,最后汇总完毕后主线程结束。

     七、总结

      在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给 Future 对象在后台完成, 当主线程将来需要时,就可以通过 Future 对象获得后台作业的计算结果或者执行状态;

      一般 FutureTask 多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果;

      仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get 方法。一旦计算完成,就不能再重新开始或取消计算。 get 方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常。
      只需要计算完成一次,下次直接获取结果,不需要再计算。

  • 相关阅读:
    [日常] Go语言圣经-命令行参数
    [日常] Go语言圣经前言
    [日常] 搭建golang开发环境
    [日常] 研究redis未授权访问漏洞利用过程
    [日常] CentOS安装最新版redis设置远程连接密码
    [日常] Apache Order Deny,Allow的用法
    [日常] 读取队列并循环发信的脚本
    [日常] 20号日常工作总结
    [日常] SinaMail项目和技术能力总结
    [日常] MySQL的预处理技术测试
  • 原文地址:https://www.cnblogs.com/niujifei/p/15835259.html
Copyright © 2020-2023  润新知