• Java线程如何返回数据


      前言

      当开发者从单线程开发模式过渡到多线程环境,一个比较棘手的问题就是如何在一个线程中返回数据,众所周知,run()方法和start()方法不会返回任何值。

    笔者在学习《Java Network Programming》一书时,总结三种常用方法:定义获取器、静态方法回调以及实例方法回调。

    定义获取器

      从线程中返回数据,比较直观的想法是在线程中定义一个get方法,线程执行完成后,调用get方法即可,表观如此,其实会遇到意想不到的结果。

      代码清单1-1 展示了在线程中定义获取器 

    package thread;
    
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.security.DigestInputStream;
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    
    /**
     * Created by Michael Wong on 2015/11/21.
     */
    public class ReturnDigest extends Thread {
    
        /** 目标文件 */
        private String fileName;
    
        /** 消息摘要 */
        private byte[] digest;
    
        public ReturnDigest(String fileName) {
            this.fileName = fileName;
        }
    
        /**
         * 计算一个256位的SHA-2消息摘要
         */
        @Override
        public void run() {
            try {
                FileInputStream fis = new FileInputStream(fileName);
                MessageDigest sha = MessageDigest.getInstance("SHA-256");
                DigestInputStream dis = new DigestInputStream(fis, sha);
                while(dis.read() != -1);    //读取整个文件
                dis.close();
                digest = sha.digest();
            } catch (IOException ex) {
                ex.printStackTrace();
            } catch (NoSuchAlgorithmException ex) {
                ex.printStackTrace();
            }
        }
    
        /**
         * 获取消息摘要
         * @return 消息摘要字节数组
         */
        public byte[] getDigest() {
            return this.digest;
        }
    
    }

      代码清单1-2展示如何在主线程调用 

    package thread;
    
    import javax.xml.bind.DatatypeConverter;
    
    /**
     * This solution is not guaranteed to work.On some virtual machines,
     * the main thread takes all the time avaiable and leaves not time for actual worker threads.
     * Created by Michael Wong on 2015/11/21.
     */
    public class ReturnDigestUserInterface {
    
        public static void main(String[] args) {
            if(args.length == 0) {
                args = new String[] {"E:/IdeaProjects/java/NetProgramming/src/thread/ReturnDigest.java",
                        "E:/IdeaProjects/java/NetProgramming/src/thread/ReturnDigestUserInterface.java"};
            }
    
            ReturnDigest[] returnDigest = new ReturnDigest[args.length];
    
            for(int i = 0; i < args.length; i++) {
                returnDigest[i] = new ReturnDigest(args[i]);
                returnDigest[i].start();
            }
    
            for(int i = 0; i < args.length; i++) {
                while(true) {
                    byte[] digest = returnDigest[i].getDigest();
                    if(digest != null) {
                        StringBuilder result = new StringBuilder(args[i]);
                        result.append(": ").append(DatatypeConverter.printHexBinary(digest));
                        System.out.println(result);
                        break;
                    }
                }
            }
    
        }
    
    }

      在这种方式中,通过一个while(true){} 循环不停的判断digest是否为空,就是指子线程是否执行完毕。如果你足够幸运,可能会得到正确的结果,但效率比较低,也有可能程序假死,这取决于虚拟机的实现。有些虚拟机,主线程会占用所有的时间,真正的工作线程根本没有机会得到执行,所以不推荐这种做法。 

    静态方法回调

      事实上,利用回调方法解决这类问题更简单高效。与其在主函数中不停的判断子线程是否执行完毕,倒不如让子线程在执行完毕时,主动通知主线程,这种思想和观察者设计模式异曲同工。

      代码清单2-1展示在子线程执行完成时调用静态回调方法 

    package thread;
    
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.security.DigestInputStream;
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    
    /**
     * @description 从线程返回信息 静态回调方法
     * Created by Administrator on 2015/11/3.
     */
    public class CallbackDigest implements Runnable {
    
        private String fileName;
    
        public CallbackDigest(String fileName) {
            this.fileName = fileName;
        }
    
        /**
         * 计算一个256位的SHA-2消息摘要
         */
        @Override
        public void run() {
            try {
                FileInputStream fis = new FileInputStream(fileName);
                MessageDigest sha = MessageDigest.getInstance("SHA-256");
                DigestInputStream dis = new DigestInputStream(fis, sha);
                while(dis.read() != -1) ;   //读取整个文件
                dis.close();
                byte[] digest = sha.digest();
                //调用主调类静态回调方法
                CallbackDigestUserInterface.receiveDigest(digest, fileName);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            }
    
        }
    }

      代码清单2-2展示主调类的静态回调方法 

    package thread;
    
    import javax.xml.bind.DatatypeConverter;
    
    /**
     * @description 静态方法回调
     * Created by Administrator on 2015/11/3.
     */
    public class CallbackDigestUserInterface {
    
        public static void receiveDigest(byte[] digest, String name) {
            StringBuilder result = new StringBuilder(name);
            result.append(": ");
            result.append(DatatypeConverter.printHexBinary(digest));
            System.out.println(result);
        }
    
        public static void main(String[] args) {
            if(args.length == 0) {
                args = new String[] {"E:/IdeaProjects/java/NetProgramming/src/thread/CallbackDigest.java",
                        "E:/IdeaProjects/java/NetProgramming/src/thread/CallbackDigestUserInterface.java"};
            }
            for(String fileName : args) {
                CallbackDigest cb = new CallbackDigest(fileName);
                Thread thread = new Thread(cb);
                thread.start();
            }
        }
    }

      静态回调方法在CallbackDigestUserInterface中定义,在子线程CalbackDigest的run方法结束前调用,将摘要打印到控制台,也可以将摘要作为主调线程的属性,通过回调方法为其赋值,再交给主调线程自身处理,实例方法回调将展示这种做法。

    实例方法回调

      所谓实例方法回调就是指进行回调的类(子线程)持有回调对象(主线程)的一个引用,主线程在调用子线程时,将自身作为参数传给子线程。通过构造函数,主线程可以传递参数给子线程。

      代码清单3-1展示在子线程中持有回调对象的引用,通过这个引用调用回调方法。 

    package thread;
    
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.security.DigestInputStream;
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    
    /**
     * @description 进行回调的类持有回调对象的一个引用
     * Created by Administrator on 2015/11/3.
     */
    public class InstanceCallbackDigest implements Runnable {
    
        /**
         * 映射文件
         */
        private String fileName;
    
        /**
         * 回调对象引用
         */
        private InstanceCallbackDigestUserInterface callbackInstance;
    
        public InstanceCallbackDigest(String fileName, InstanceCallbackDigestUserInterface callbackInstance) {
            this.fileName = fileName;
            this.callbackInstance = callbackInstance;
        }
    
        @Override
        public void run() {
            try {
                FileInputStream fis = new FileInputStream(fileName);
                MessageDigest sha = MessageDigest.getInstance("SHA-256");
                DigestInputStream dis = new DigestInputStream(fis, sha);
                while(dis.read() != -1);
                dis.close();
                byte[] digest = sha.digest();
                callbackInstance.receiveDigest(digest);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            }
        }
    }

      代码清单3-2展示回调对象类 

    package thread;
    
    import javax.xml.bind.DatatypeConverter;
    
    /**
     * @description 实例方法回调
     * Created by Administrator on 2015/11/3.
     */
    public class InstanceCallbackDigestUserInterface {
    
        /**
         * 映射文件
         */
        private String fileName;
    
        /**
         * 摘要
         */
        private byte[] digest;
    
        public InstanceCallbackDigestUserInterface(String fileName) {
            this.fileName = fileName;
        }
    
        public void calculateDigest() {
            InstanceCallbackDigest cb = new InstanceCallbackDigest(fileName, this);
            Thread t = new Thread(cb);
            t.start();
            try  {
                t.join();
            } catch(InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        protected void receiveDigest(byte[] digest) {
            this.digest = digest;
        }
    
        public String getDigest() {
            String result = fileName + ": ";
            if (digest == null) {
                result += "digest not available";
            } else {
                result += DatatypeConverter.printHexBinary(digest);
            }
            return result;
        }
    
        public static void main(String[] args) {
            if(args.length == 0) {
                args = new String[] {"E:/IdeaProjects/java/NetProgramming/src/thread/InstanceCallbackDigest.java",
                        "E:/IdeaProjects/java/NetProgramming/src/thread/InstanceCallbackDigestUserInterface.java"};
            }
            for(String fileName : args) {
                InstanceCallbackDigestUserInterface instance = new InstanceCallbackDigestUserInterface(fileName);
                instance.calculateDigest();
                System.out.println(instance.getDigest());
            }
        }
    }

      回调方法receiveDigest()只是接受计算完后的摘要数据,真正启动子线程的是calculateDigest()方法。通过调用子线程的构造函数,将文件名称和自身应用传递给子线程。在子线程启动(调用start方法)后,又调用子线程的join方法,join会把指定线程加入到当前线程,将两个并行执行的线程合并为顺序执行。此处会把主线程加入到子线程,这样做的目的是:在主线程调用calculateDigest()交给子线程去计算摘要,并赋给digest,在主线程调用getDigest()获取digest,如果并行执行,在主线程调用getDigest时,子线程可能还没有执行结束,digest就会为null。

    总结

      第一种方式:定义获取器,不推荐使用,结果是否正确取决于虚拟机线程调度等相关设计。

      第二种方式:静态回调方法,简单易懂,对于简单的打印输出有效,对于复杂的需求比较无力。

      第三种方式:实例方法回调,推荐使用,功能比较丰富,既可以向子线程传递参数,也可以从子线程取回数据,正所谓礼尚往来,来而不往非礼也。而且对数据如何处理的自主权掌握在主线程手里(程序猿都有很强的控制欲~v~)。 


    作者:VictorWong
    出处:http://www.cnblogs.com/jwongo
    github:https://github.com/jwongo
    本文版权归作者和博客园共有,欢迎转载。水平有限,恳请读者批评指正。如果觉得对您有点用,就点个赞支持一下吧。

  • 相关阅读:
    条款14:在资源管理类中心copying行为(Think carefully about copying behavior in resource-manage classes)
    matlab ()的用法
    正式学习React(五) Reactjs 的 PropTypes 使用方法
    正式学习 react(三)
    webpack ------require,ensure
    转的git
    Session机制详解
    ES5 object的新函数
    HDU 4635 Strongly connected (强连通分量)
    HDU 4635 Strongly connected (强连通分量)
  • 原文地址:https://www.cnblogs.com/jwongo/p/java-thread-return-information.html
Copyright © 2020-2023  润新知