首页 > 解决方案 > 关于 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 线程是否完成,不如让线程告诉主程序何时完成

查看代码,为运行单独计算而创建的线程实际上是继续执行原始程序的线程。它不像将结果传递回主线程。它似乎成为主线程!

因此,当任务完成时,主线程并没有得到通知(而不是主线程轮询)。就是主线程不关心结果。它运行到尽头并完成。新线程完成后只会运行另一个计算。

我理解正确吗?

这对调试有什么作用?该线程现在是否成为主线程?调试器现在会这样对待它吗?

是否有另一种方法可以将结果实际传递回主线程?

我将不胜感激任何帮助,这有助于更好地理解这一点:)

标签: javamultithreading

解决方案


认为“主”线程,即public static void main运行在其上的线程,应该被视为应用程序的主线程,这是一种常见的误解。例如,如果您编写一个 gui 应用程序,启动线程可能会在程序结束之前完成并死掉。

此外,回调通常由它们被移交给的线程调用。这在 Swing 和许多其他地方都是如此(例如,包括DataFetcher


推荐阅读