习惯了传统使用Main单线程过程式模型的程序员在转向多线程环境时,最难掌握的就是如何从线程中获取返回信息。从结束的线程获得信息,这是多线程编程中最常误解的方面之一,run()方法和start()方法不返回任何值。接下来,我们逐一讲解几种获取返回信息的方法。整个程序设计,简单地显示SHA-256文件地址信息,文件线程需要把文件地址返回给执行主线程Main。
- 最直接的方式:线程与信息显示同时进行
线程调用函数:
import java.io.FileInputStream;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import com.gy.test.CallbackDigestUserInterface;
public class ReturnDigest extends Thread {
private String filename;
private byte[] digest;
public ReturnDigest(String filename) {
// TODO Auto-generated constructor stub
this.filename = filename;
}
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
try {
FileInputStream in = new FileInputStream(filename);
MessageDigest sha = MessageDigest.getInstance("SHA-256");
DigestInputStream din = new DigestInputStream(in, sha);
while (din.read() != -1) ;
din.close();
digest = sha.digest();
} catch (Exception e) {
// TODO: handle exception
System.err.println(e);
}
}
public byte[] getDigest() {
return digest;
}
}
主线程:
import javax.xml.bind.DatatypeConverter;
import com.gy.returndigest.ReturnDigest;
public class ReturnDigestUserInterface {
public static void main(String[] args) {
String[] files = {"F:\\MySQL\\eclipse\\test.txt",
"F:\\MySQL\\eclipse\\test1.txt",
"F:\\MySQL\\eclipse\\test2.txt"};
for (String filename: files) {
ReturnDigest dr = new ReturnDigest(filename);
dr.start();
StringBuilder result = new StringBuilder(filename);
System.out.println(result);
result.append(": ");
byte[] digest = dr.getDigest();
result.append(DatatypeConverter.printHexBinary(digest));
System.out.println(result);
}
}
}
输出结果:
结果显示,空指针错误。问题在于,主程序会在线程有机会初始化文件地址信息之前就获取并使用信息。在单线程中,main()函数调用dr.getDigest()方法之前结束,也可能没有结束,导致结果无法获取到,出现错误。
- 竞态条件:一种可能方法把dr.getDigest()调用移动到main()方法
主代码:
import javax.xml.bind.DatatypeConverter;
import com.gy.returndigest.ReturnDigest;
public class ReturnDigestUserInterface {
public static void main(String[] args) {
String[] files = {"F:\\MySQL\\eclipse\\test.txt",
"F:\\MySQL\\eclipse\\test1.txt",
"F:\\MySQL\\eclipse\\test2.txt"};
ReturnDigest[] digests = new ReturnDigest[files.length];
for (int i = 0; i < files.length; i++) {
digests[i] = new ReturnDigest(files[i]);
digests[i].start();
}
for (int i = 0; i < digests.length; i++) {
StringBuilder result = new StringBuilder(files[i]);
System.out.println(result);
result.append(": ");
byte[] digest = digests[i].getDigest();
result.append(DatatypeConverter.printHexBinary(digest));
System.out.println(result);
}
}
}
结果显示如第一种方法一样,如果你有幸的化是可以获取到结果,但是我没有获得过。。。
- 轮询方式:获取方法返回一个标志位,直到获取到结果字段为止
代码如下:
import javax.xml.bind.DatatypeConverter;
import com.gy.returndigest.ReturnDigest;
public class ReturnDigestUserInterface {
public static void main(String[] args) {
String[] files = {"F:\\MySQL\\eclipse\\test.txt",
"F:\\MySQL\\eclipse\\test1.txt",
"F:\\MySQL\\eclipse\\test2.txt"};
ReturnDigest[] digests = new ReturnDigest[files.length];
for (int i = 0; i < files.length; i++) {
digests[i] = new ReturnDigest(files[i]);
digests[i].start();
}
for (int i = 0; i < digests.length; i++) {
while (true) {
byte[] digest = digests[i].getDigest();
if (digest != null) {
StringBuilder result = new StringBuilder(files[i]);
System.out.println(result);
result.append(": ");
result.append(DatatypeConverter.printHexBinary(digest));
System.out.println(result);
break;
}
}
}
}
}
结果显示:
可以看出,我们获取到了所得加密信息。但是这个解决方法不能保证一定能工作。在有些虚拟机上,主线程会占用所有可能用的时间,而没有给具体的工作线程流出任何时间。主线程太忙于检查工作的完成情况,以至于没有时间来具体完成任务。
- 回调方法:通过调用主类中的一个方法,称为回调
当线程的run()方法接近结束时候,要做的最后一件事情就是基于结果调用主程序中的一个已知方法。
线程代码如下:
import java.io.FileInputStream;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import com.gy.test.CallbackDigestUserInterface;
public class CallbackDigest implements Runnable {
private String filename;
public CallbackDigest(String filename) {
// TODO Auto-generated constructor stub
this.filename = filename;
}
@Override
public void run() {
// TODO Auto-generated method stub
try {
FileInputStream in = new FileInputStream(filename);
MessageDigest sha = MessageDigest.getInstance("SHA-256");
DigestInputStream din = new DigestInputStream(in, sha);
while (din.read() != -1) ;
din.close();
//digest = sha.digest();
byte[] digest = sha.digest();
CallbackDigestUserInterface.receiveDigest(digest, filename);
} catch (Exception e) {
// TODO: handle exception
System.err.println(e);
}
}
}
主函数代码如下:
import javax.xml.bind.DatatypeConverter;
import com.gy.returndigest.CallbackDigest;
public class CallbackDigestUserInterface {
public static void receiveDigest(byte[] digest, String name) {
StringBuilder result = new StringBuilder(name);
System.out.println(result);
result.append(": ");
result.append(DatatypeConverter.printHexBinary(digest));
System.out.println(result);
}
public static void main(String[] args) {
String[] files = {"F:\\MySQL\\eclipse\\test.txt",
"F:\\MySQL\\eclipse\\test1.txt",
"F:\\MySQL\\eclipse\\test2.txt"};
for (int i = 0; i < files.length; i++) {
CallbackDigest cb = new CallbackDigest(files[i]);
Thread thread = new Thread(cb);
thread.start();
}
}
}
结果显示:
我们可以看出程序是顺序加载不同的文件地址信息,然后顺序执行加密输出结果,达到使用线程顺序输出结果的目的。
- Callable带返回值的线程运算
我们也可以使用带有返回值的线程进行加密输出,这样就是可以保证获取一条信息,就进行加密一次。
线程函数代码:
import java.io.FileInputStream;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.util.concurrent.Callable;
import javax.xml.bind.DatatypeConverter;
public class CallableThread implements Callable<String> {
private String filename;
private String str;
public CallableThread(String filename) {
// TODO Auto-generated constructor stub
this.filename = filename;
}
@Override
public String call() throws Exception {
// TODO Auto-generated method stub
try {
FileInputStream in = new FileInputStream(filename);
MessageDigest sha = MessageDigest.getInstance("SHA-256");
DigestInputStream din = new DigestInputStream(in, sha);
while (din.read() != -1) ;
din.close();
byte[] digest = sha.digest();
StringBuilder result = new StringBuilder(filename);
System.out.println(result);
result.append(": ");
result.append(DatatypeConverter.printHexBinary(digest));
str = new String(result);
} catch (Exception e) {
// TODO: handle exception
System.err.println(e);
}
return str;
}
}
主函数如下:
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import com.gy.returndigest.CallableThread;
public class Main {
public static void main(String[] args) {
String[] files = {"F:\\MySQL\\eclipse\\test.txt",
"F:\\MySQL\\eclipse\\test1.txt",
"F:\\MySQL\\eclipse\\test2.txt"};
for (int i = 0; i < files.length; i++) {
CallableThread ct = new CallableThread(files[i]);
FutureTask<String> ft = new FutureTask<>(ct);
new Thread(ft, "返回值").start();
try {
System.out.println(ft.get());
} catch (Exception e) {
// TODO: handle exception
System.out.println(e);
}
}
}
}
结果如下所示:
总结
在顺序执行编程时候,顺序执行程序思想是想到的主要思想。但是多线程运行以后,由于主线程与子线程是并发运行,很难保障获得期望结果,所以深入了解并发线程思想,获取我们想获得的程序信息。