前言

  当开发者从单线程开发模式过渡到多线程环境,一个比较棘手的问题就是如何在一个线程中返回数据,众所周知,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~)。

Java线程如何返回数据的更多相关文章

  1. 在Java 线程中返回值的用法

    http://icgemu.iteye.com/blog/467848 在Java 线程中返回值的用法 博客分类: Java Javathread  有时在执行线程中需要在线程中返回一个值:常规中我们 ...

  2. Java线程安全与数据同步

    import java.util.HashMap; import java.util.concurrent.TimeUnit; public class Test { public static vo ...

  3. java线程基础巩固---数据同步引入并结合jconsole,jstack以及汇编指令认识synchronized关键字

    对于多线程编程而言其实老生成谈的就是数据同步问题,接下来就会开始接触这块的东东,比较麻烦,但是也是非常重要,所以按部就班的一点点去专研它,下面开始. 数据同步引入: 这里用之前写过的银行叫号的功能做为 ...

  4. java geteway 手机返回数据

    import lombok.extern.slf4j.Slf4j; import org.reactivestreams.Publisher; import org.springframework.c ...

  5. Java多线程初学者指南(8):从线程返回数据的两种方法

    从线程中返回数据和向线程传递数据类似.也可以通过类成员以及回调函数来返回数据.但类成员在返回数据和传递数据时有一些区别,下面让我们来看看它们区别在哪. 一.通过类变量和方法返回数据 使用这种方法返回数 ...

  6. java线程实现的四种方式

    java多线程的实现可以通过以下四种方式 1.继承Thread类,重写run方法 2.实现Runnable接口,重写run方法 3.通过Callable和FutureTask创建线程 4.通过线程池创 ...

  7. Java并发工具类(四):线程间交换数据的Exchanger

    简介 Exchanger(交换者)是一个用于线程间协作的工具类.Exchanger用于进行线程间的数据交换.它提供一个同步点,在这个同步点两个线程可以交换彼此的数据.这两个线程通过exchange方法 ...

  8. java URL实现调用其他系统发送报文并获取返回数据

    模拟本系统通过Url方式发送报文到目标服务器,并获取返回数据:(实现类) import java.io.BufferedOutputStream; import java.io.BufferedRea ...

  9. Java线程与并发库高级应用-线程范围内共享数据ThreadLocal类

    1.线程范围内共享变量 1.1 前奏: 使用一个Map来实现线程范围内共享变量 public class ThreadScopeShareData { static Map<Thread, In ...

随机推荐

  1. QMVC

    高性能.NET MVC之QMVC! ASP.NET!这个词代表者一个单词Fat!因为他总是捆绑着太多的太多的类,太多太多的各种功能!你也许会用到,如果你反编译或阅读他们开源的源码,你会不会犹如在大海中 ...

  2. ArcGIS 10.1 发布使用ArcEngine自定义的GP服务

    1. 新建立GP模型 在VS2010中新建一个普通的程序及,引入ArcEngine相关的dll.在该DLL中定义一个或多个GP类和一个GP工厂类.GP类要继承IGPFunction2接口,GP工厂类要 ...

  3. SZU : A18 (Climb Well)

    Judge Info Memory Limit: 32768KB Case Time Limit: 10000MS Time Limit: 10000MS Judger: Number Only Ju ...

  4. iOS基础 - iOS网络基础

    一.网络访问的四个步骤 l 第一步:确定地址URL l 第二步:建立请求 l 第三步:建立并启动连接 l 第四步:连接完成,处理结果 备注:URL,统一资源定位符(URL,英语Uniform Reso ...

  5. c# in deep 之Lambda表达式于LINQ表达式结合后令人惊叹的简洁(2)

    当Lambda表达式和LINQ一起使用时,我们会发现原本冗长的代码会变得如此简单.比如我们要打印0-10之间的奇数,让其从高到低排列并求其平方根,现在只用一行代码即可完成其集合的生成,直接上代码: v ...

  6. AngularJS框架速写

    最近在把玩AngularJS框架,之前也看过流行的Backbone,不过AngularJS给人的感觉完全不同,它走的是一条高帅富之路. 按照官方的说法,AngularJS是一套依赖注入的MVC开发套件 ...

  7. IOS学习之路十七(通过delegate进行页面传值)

    加入有A B两个页面,要实现从A跳到B的时候把值传过去,现在用delegate协议来实现 在A中定义一个协议,定义一个实现该协议的属性变量 在B中定义一个值(要获得的值类型)和set方法. 要传值B ...

  8. opencv中的vs框架中的Blob Tracking Tests的中文注释。

    经过2天的努力终于算是大概弄清楚了opencv中的vs框架是大概是如何工作的了,下面贴一下我自己写的代码注释.非常详细.对初学者有帮助.尤其详细分析了RunBlobTrackingAuto()函数,在 ...

  9. 什么是Numpy的ndarray

    什么是Numpy的ndarray 首先,Numpy的核心是ndarray. 然后,ndarray本质是数组,其不同于一般的数组,或者Python 的list的地方在于它可以有N 维(dimention ...

  10. .Net里的Attribute 学习

    .Net里的Attribute 学习 前两天看到书里边讲Attribute定制,结合了网上的资料,自己做了简单的登录功能,并结合了某些设计模式,有兴趣的朋友可以看下.由于时间原因,没有做过多的说明,直 ...