java线程返回参数吗_Java线程如何返回数据

前言

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

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

定义获取器

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

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

packagethread;importjava.io.FileInputStream;importjava.io.IOException;importjava.security.DigestInputStream;importjava.security.MessageDigest;importjava.security.NoSuchAlgorithmException;/*** Created by Michael Wong on 2015/11/21.*/

public class ReturnDigest extendsThread {/**目标文件*/

privateString fileName;/**消息摘要*/

private byte[] digest;publicReturnDigest(String fileName) {this.fileName =fileName;

}/*** 计算一个256位的SHA-2消息摘要*/@Overridepublic voidrun() {try{

FileInputStream fis= newFileInputStream(fileName);

MessageDigest sha= MessageDigest.getInstance("SHA-256");

DigestInputStream dis= newDigestInputStream(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展示如何在主线程调用

packagethread;importjavax.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 classReturnDigestUserInterface {public static voidmain(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= newReturnDigest[args.length];for(int i = 0; i < args.length; i++) {

returnDigest[i]= newReturnDigest(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= newStringBuilder(args[i]);

result.append(": ").append(DatatypeConverter.printHexBinary(digest));

System.out.println(result);break;

}

}

}

}

}

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

静态方法回调

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

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

packagethread;importjava.io.FileInputStream;importjava.io.IOException;importjava.security.DigestInputStream;importjava.security.MessageDigest;importjava.security.NoSuchAlgorithmException;/*** @description 从线程返回信息 静态回调方法

* Created by Administrator on 2015/11/3.*/

public class CallbackDigest implementsRunnable {privateString fileName;publicCallbackDigest(String fileName) {this.fileName =fileName;

}/*** 计算一个256位的SHA-2消息摘要*/@Overridepublic voidrun() {try{

FileInputStream fis= newFileInputStream(fileName);

MessageDigest sha= MessageDigest.getInstance("SHA-256");

DigestInputStream dis= newDigestInputStream(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展示主调类的静态回调方法

packagethread;importjavax.xml.bind.DatatypeConverter;/*** @description 静态方法回调

* Created by Administrator on 2015/11/3.*/

public classCallbackDigestUserInterface {public static void receiveDigest(byte[] digest, String name) {

StringBuilder result= newStringBuilder(name);

result.append(": ");

result.append(DatatypeConverter.printHexBinary(digest));

System.out.println(result);

}public static voidmain(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= newCallbackDigest(fileName);

Thread thread= newThread(cb);

thread.start();

}

}

}

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

实例方法回调

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

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

packagethread;importjava.io.FileInputStream;importjava.io.IOException;importjava.security.DigestInputStream;importjava.security.MessageDigest;importjava.security.NoSuchAlgorithmException;/*** @description 进行回调的类持有回调对象的一个引用

* Created by Administrator on 2015/11/3.*/

public class InstanceCallbackDigest implementsRunnable {/*** 映射文件*/

privateString fileName;/*** 回调对象引用*/

privateInstanceCallbackDigestUserInterface callbackInstance;publicInstanceCallbackDigest(String fileName, InstanceCallbackDigestUserInterface callbackInstance) {this.fileName =fileName;this.callbackInstance =callbackInstance;

}

@Overridepublic voidrun() {try{

FileInputStream fis= newFileInputStream(fileName);

MessageDigest sha= MessageDigest.getInstance("SHA-256");

DigestInputStream dis= newDigestInputStream(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展示回调对象类

packagethread;importjavax.xml.bind.DatatypeConverter;/*** @description 实例方法回调

* Created by Administrator on 2015/11/3.*/

public classInstanceCallbackDigestUserInterface {/*** 映射文件*/

privateString fileName;/*** 摘要*/

private byte[] digest;publicInstanceCallbackDigestUserInterface(String fileName) {this.fileName =fileName;

}public voidcalculateDigest() {

InstanceCallbackDigest cb= new InstanceCallbackDigest(fileName, this);

Thread t= newThread(cb);

t.start();try{

t.join();

}catch(InterruptedException e) {

e.printStackTrace();

}

}protected void receiveDigest(byte[] digest) {this.digest =digest;

}publicString getDigest() {

String result= fileName + ": ";if (digest == null) {

result+= "digest not available";

}else{

result+=DatatypeConverter.printHexBinary(digest);

}returnresult;

}public static voidmain(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= newInstanceCallbackDigestUserInterface(fileName);

instance.calculateDigest();

System.out.println(instance.getDigest());

}

}

}

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

总结

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

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值