java - 关于 Java 中的线程和回调的问题
问题描述
我正在阅读Elliotte 的 Java 网络编程,在关于线程的章节中,他给出了这段代码作为可以在不同线程中运行的计算示例
import java.io.*;
import java.security.*;
public class ReturnDigest extends Thread {
private String filename;
private byte[] digest;
public ReturnDigest(String filename) {
this.filename = filename;
}
@Override
public void run() {
try {
FileInputStream in = new FileInputStream(filename);
MessageDigest sha = MessageDigest.getInstance("SHA-256");
DigestInputStream din = new DigestInputStream(in, sha);
while (din.read() != -1) ; // read entire file
din.close();
digest = sha.digest();
} catch (IOException ex) {
System.err.println(ex);
} catch (NoSuchAlgorithmException ex) {
System.err.println(ex);
}
}
public byte[] getDigest() {
return digest;
}
}
为了使用这个线程,他给出了一种他称之为新手可能使用的解决方案的方法。
大多数新手采用的解决方案是让 getter 方法返回一个标志值(或者可能抛出异常),直到设置了结果字段。
他所指的解决方案是:
public static void main(String[] args) {
ReturnDigest[] digests = new ReturnDigest[args.length];
for (int i = 0; i < args.length; i++) {
// Calculate the digest
digests[i] = new ReturnDigest(args[i]);
digests[i].start();
}
for (int i = 0; i < args.length; i++) {
while (true) {
// Now print the result
byte[] digest = digests[i].getDigest();
if (digest != null) {
StringBuilder result = new StringBuilder(args[i]);
result.append(": ");
result.append(DatatypeConverter.printHexBinary(digest));
System.out.println(result);
break;
}
}
}
}
然后,他继续提出使用回调的更好方法,他将其描述为:
事实上,有一种更简单、更有效的方法来处理这个问题。可以消除重复轮询每个 ReturnDigest 对象以查看其是否完成的无限循环。诀窍在于,与其让主程序反复询问每个 ReturnDigest 线程是否完成(就像一个 5 岁的孩子在长途汽车旅行中反复询问“我们到了吗?”,而且几乎同样烦人),您让线程在完成时告诉主程序。它通过调用启动它的主类中的方法来实现这一点。这称为回调,因为线程在完成后会回调其创建者
他给出的回调方法的代码如下:
import java.io.*;
import java.security.*;
public class CallbackDigest implements Runnable {
private String filename;
public CallbackDigest(String filename) {
this.filename = filename;
}
@Override
public void run() {
try {
FileInputStream in = new FileInputStream(filename);
MessageDigest sha = MessageDigest.getInstance("SHA-256");
DigestInputStream din = new DigestInputStream( in , sha);
while (din.read() != -1); // read entire file
din.close();
byte[] digest = sha.digest();
CallbackDigestUserInterface.receiveDigest(digest, filename); // this is the callback
} catch (IOException ex) {
System.err.println(ex);
} catch (NoSuchAlgorithmException ex) {
System.err.println(ex);
}
}
}
其实现CallbackDigestUserInterface
及其用法如下:
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) {
for (String filename: args) {
// Calculate the digest
CallbackDigest cb = new CallbackDigest(filename);
Thread t = new Thread(cb);
t.start();
}
}
}
但我的问题(或澄清)是关于他对这种方法所说的......他提到
诀窍是,与其让主程序反复询问每个 ReturnDigest 线程是否完成,不如让线程告诉主程序何时完成
查看代码,为运行单独计算而创建的线程实际上是继续执行原始程序的线程。它不像将结果传递回主线程。它似乎成为主线程!
因此,当任务完成时,主线程并没有得到通知(而不是主线程轮询)。就是主线程不关心结果。它运行到尽头并完成。新线程完成后只会运行另一个计算。
我理解正确吗?
这对调试有什么作用?该线程现在是否成为主线程?调试器现在会这样对待它吗?
是否有另一种方法可以将结果实际传递回主线程?
我将不胜感激任何帮助,这有助于更好地理解这一点:)
解决方案
认为“主”线程,即public static void main
运行在其上的线程,应该被视为应用程序的主线程,这是一种常见的误解。例如,如果您编写一个 gui 应用程序,启动线程可能会在程序结束之前完成并死掉。
此外,回调通常由它们被移交给的线程调用。这在 Swing 和许多其他地方都是如此(例如,包括DataFetcher)
推荐阅读
- docker - 在通过我的 github 操作上的矩阵构建自定义图像之前,如何 docker pull 常用的 docker 图像?
- eslint - 带别名的 ESLint
- reporting-services - 使用 T-SQL 执行报表数据驱动订阅
- powershell - 在同一个powershell会话中一个接一个地执行两个命令
- python - 如何使用 FOR 循环生成对象?
- laravel - 为什么 laravel $request->file 返回 null?
- python - 如何使这个复杂的对象 JSON 可序列化?
- amcharts - 在 amcharts 4 中,删除条形图中的底部错误栏
- snowflake-cloud-data-platform - 使用阶段失败的文件创建 Snowpipe 副本
- c - Mingw 在编译代码时给了我一个 libwinpthread-1.dll was not found 错误