前言

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

    Strategic Game                                                         Time Limit: 20000/10000 MS (J ...

  2. Javascript多线程引擎(三)

    Javascript多线程引擎(三) 完成对ECMAScript-262 3rd规范的阅读后, 列出了如下的限制条件 1. 去除正则表达式( 语法识别先不编写) 2. 去除对Function Decl ...

  3. WCF、Web API、WCF REST、Web Service 区别

    Web Service It is based on SOAP and return data in XML form. It support only HTTP protocol. It is no ...

  4. iOS基础 - 触摸事件与手势识别

    一.iOS的输入事件 UIKit可识别三种类型的输入事件: 触摸事件 运动(加速计)事件 远程控制事件 二.UIEvent iOS中许多事件对象都是UIEvent类的实例,记录事件产生的时刻和类型 U ...

  5. -协同IResult

    Caliburn.Micro学习笔记(五)----协同IResult   今天说一下协同IResult 看一下IResult接口 /// <summary> /// Allows cust ...

  6. WPF专业编程指南 - 那些根元素<>的解释

    <Window x:Class="Chapter1_WpfApplication1.MainWindow" xmlns="http://schemas.micros ...

  7. Cocos2d学习之路五(Box2d使用CCPhysicsSprite时编译不通过解决方法)

    cocos2d使用box2d引擎,在使用CCPhysicsSprite添加精灵的时候会出现编译不通过错误. 需要注意以下几点: 1.sprite.position=ccp(p.x,p.y);这行代码一 ...

  8. window.open()详解及浏览器兼容性问题

    一.基本语法:window.open(pageURL,name,parameters)其中:pageURL 为子窗口路径name  为子窗口名字parameters 为窗口参数(各参数用逗号分隔) 二 ...

  9. JS获取ckeditor4.x里的值

    项目中有这样一个需求,使用ckeditor可以上传图片,需要在前端验证一下不可上传多于5张图片. 以下是查看源代码所看到的ckeditor里的值 <p>AAAAA</p> &l ...

  10. Power BI移动端应用

    随笔- 420  文章- 6  评论- 1927  无处不在的商业智能---Power BI移动端应用   此篇来自于微软商业智能网站的官方博客团队发布的Power BI在线资料其中的一部分,完整版地 ...