zoukankan      html  css  js  c++  java
  • 如何从线程返回信息——轮询、回调、Callable

    考虑有这样一个LiftOff类:

    复制代码
    /**
     * 类LiftOff.java的实现描述:显示发射之前的倒计时
     * 
     */
    public class LiftOff implements Runnable {
    
        public LiftOff(){
            taskCount++;// 计数自增
        }
    
        private int        countDown = 3;        // 倒计时数字
    
        private static int taskCount = 0;
    
        private int        id        = taskCount;
    
        @Override
        public void run() {
    
            while (countDown >= 0) {
                System.out.println("线程编号" + id + "--倒计时" + countDown);
                countDown--;
                Thread.yield();
            }
        }
    }
    复制代码

      以及一个发射主线程:

    复制代码
    public class Launch {
    
        public static void main(String[] args) {
            LiftOff liftOff = new LiftOff();
            Thread t = new Thread(liftOff);
            t.start();
            System.out.println("发射!");
        }
    }
    复制代码

      我们的本意是先显示倒计时,然后显示“发射!”,运行结果却是

    发射!
    线程编号0--倒计时3
    线程编号0--倒计时2
    线程编号0--倒计时1
    线程编号0--倒计时0

      因为main()函数也是一个线程,程序能否得到正确的结果依赖于线程的相对执行速度,而我们无法控制这一点。想要使LiftOff线程执行完毕后再继续执行主线程,比较容易想到的办法是使用轮询

    按 Ctrl+C 复制代码
    按 Ctrl+C 复制代码

      我们添加了isOver变量,在倒计时结束时将isOver置为true,主函数中我们不断地判断isOver的状态,就可以判断LiftOff线程是否执行完毕:

    按 Ctrl+C 复制代码
    按 Ctrl+C 复制代码

      执行main(),输出:

    线程编号0--倒计时3
    线程编号0--倒计时2
    线程编号0--倒计时1
    线程编号0--倒计时0
    发射!

      这个解决方案是可行的,它会以正确的顺序给出正确的结果,但是不停地查询不仅浪费性能,并且有可能会因主线程太忙于检查工作的完成情况,以至于没有给具体的工作线程留出时间,更好的方式是使用回调(callback),在线程完成时反过来调用其创建者,告诉其工作已结束:

    复制代码
    public class LiftOff implements Runnable {
        
        private Launch launch;
    
        public LiftOff(Launch launch){
            taskCount++;// 计数自增
            this.launch = launch;
        }
    
        private int        countDown = 3;        // 倒计时数字
    
        private static int taskCount = 0;
    
        private int        id        = taskCount;
        
        @Override
        public void run() {
    
            while (countDown >= 0) {
                System.out.println("线程编号" + id + "--倒计时" + countDown);
                countDown--;
                if(countDown < 0){
                    launch.callBack();
                }
                Thread.yield();
            }
        }
    }
    复制代码

      主线程代码:

    复制代码
    public class Launch {
        
        public void callBack(){
            System.out.println("发射!");
        }
        
        public static void main(String[] args) {
            
            Launch launch = new Launch();
            LiftOff liftOff = new LiftOff(launch);
            
            Thread t = new Thread(liftOff);
            t.start(); 
        }
    }
    复制代码

      运行结果:

    线程编号0--倒计时3
    线程编号0--倒计时2
    线程编号0--倒计时1
    线程编号0--倒计时0
    发射!

      相比于轮询机制,回调机制的第一个优点是不会浪费那么多的CPU性能,但更重要的优点是回调更灵活,可以处理涉及更多线程,对象和类的更复杂的情况。

      例如,如果有多个对象对线程的计算结果感兴趣,那么线程可以保存一个要回调的对象列表,这些对计算结果感兴趣的对象可以通过调用方法把自己添加到这个对象列表中完成注册。当线程处理完毕时,线程将回调这些对计算结果感兴趣的对象。我们可以定义一个新的接口,所有这些类都要实现这个新接口,这个新接口将声明回调方法。这种机制有一个更一般的名字:观察者(Observer)设计模式。

    Callable

      java5引入了多线程编程的一个新方法,可以更容易地处理回调。任务可以实现Callable接口而不是Runnable接口,通过Executor提交任务并且会得到一个Future,之后可以向Future请求得到任务结果:

    复制代码
    public class LiftOff implements Callable<String> {
        
        public LiftOff(){
            taskCount++;// 计数自增
        }
    
        private int        countDown = 3;        // 倒计时数字
    
        private static int taskCount = 0;
    
        private int        id        = taskCount;
        
        @Override
        public String call() throws Exception {
            while (countDown >= 0) {
                System.out.println("线程编号" + id + "--倒计时" + countDown);
                countDown--;
            }
            return "线程编号" + id + "--结束";
        }
    }
    复制代码

      主函数:

    复制代码
    public class Launch {
        
        public static void main(String[] args) {
            
            ExecutorService executor = Executors.newCachedThreadPool();
            Future<String> future = executor.submit(new LiftOff());
            try {
                String s = future.get();
                System.out.println(s);
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
            System.out.println("发射!");
        }
    }
    复制代码

      运行结果:

    复制代码
    线程编号0--倒计时3
    线程编号0--倒计时2
    线程编号0--倒计时1
    线程编号0--倒计时0
    线程编号0--结束
    发射!
    复制代码

      容易使用Executor提交多个任务:

    复制代码
    public class Launch {
    
        public static void main(String[] args) {
    
            ExecutorService executor = Executors.newCachedThreadPool();
            List<Future<String>> results = new ArrayList<>();
            
            //多线程执行三个任务
            for (int i = 0; i < 3; i++) {
                Future<String> future = executor.submit(new LiftOff());
                results.add(future);
            }
            
            //获得线程处理结果
            for (Future<String> result : results) {
                try {
                    String s = result.get();
                    System.out.println(s);
                } catch (InterruptedException | ExecutionException e) {
                    e.printStackTrace();
                }
            }
            
            //继续主线程流程
            System.out.println("发射!");
        }
    }
    复制代码

      结果:

    复制代码
    线程编号0--倒计时3
    线程编号0--倒计时2
    线程编号0--倒计时1
    线程编号0--倒计时0
    线程编号2--倒计时3
    线程编号2--倒计时2
    线程编号1--倒计时3
    线程编号1--倒计时2
    线程编号1--倒计时1
    线程编号1--倒计时0
    线程编号2--倒计时1
    线程编号2--倒计时0
    线程编号0--结束
    线程编号1--结束
    线程编号2--结束
    发射!
    复制代码

      可以看到,Future的get()方法,如果线程的结果已经准备就绪,会立即得到这个结果,如果还没有准备好,轮询线程会阻塞,直到结果准备就绪。

    好处

      使用Callable,我们可以创建很多不同的线程,然后按照需要的顺序得到我们想要的答案。另外如果有一个很耗时的计算问题,我们也可以把计算量分到多个线程中去处理,最后汇总每个线程的处理结果,从而节省时间。

  • 相关阅读:
    052 01 Android 零基础入门 01 Java基础语法 05 Java流程控制之循环结构 14 Eclipse下程序调试——debug2 多断点调试程序
    051 01 Android 零基础入门 01 Java基础语法 05 Java流程控制之循环结构 13 Eclipse下程序调试——debug入门1
    050 01 Android 零基础入门 01 Java基础语法 05 Java流程控制之循环结构 12 continue语句
    049 01 Android 零基础入门 01 Java基础语法 05 Java流程控制之循环结构 11 break语句
    048 01 Android 零基础入门 01 Java基础语法 05 Java流程控制之循环结构 10 案例——阶乘的累加和
    047 01 Android 零基础入门 01 Java基础语法 05 Java流程控制之循环结构 09 嵌套while循环应用
    046 01 Android 零基础入门 01 Java基础语法 05 Java流程控制之循环结构 08 for循环的注意事项
    045 01 Android 零基础入门 01 Java基础语法 05 Java流程控制之循环结构 07 for循环应用及局部变量作用范围
    剑指OFFER----面试题04.二维数组中的查找
    剑指OFFER----面试题03. 数组中重复的数字
  • 原文地址:https://www.cnblogs.com/felixzh/p/6027469.html
Copyright © 2011-2022 走看看