有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

关于Java中线程和回调的多线程问题

我正在阅读Network Programming in Java by Elliotte,在关于线程的章节中,他给出了这段代码,作为可以在不同线程中运行的计算的示例

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;
 }
}

为了使用这个线程,他给出了一种方法,他称之为新手可能使用的解决方案

The solution most novices adopt is to make the getter method return a flag value (or perhaps throw an exception) until the result field is set.

他所指的解决方案是:

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;
   }
  }
 }
}

然后,他提出了一种使用回调的更好方法,他将其描述为:

In fact, there’s a much simpler, more efficient way to handle the problem. The infinite loop that repeatedly polls each ReturnDigest object to see whether it’s finished can be eliminated. The trick is that rather than having the main program repeatedly ask each ReturnDigest thread whether it’s finished (like a five-year-old repeatedly asking, “Are we there yet?” on a long car trip, and almost as annoying), you let the thread tell the main program when it’s finished. It does this by invoking a method in the main class that started it. This is called a callback because the thread calls its creator back when it’s done

他给出的回调方法代码如下:

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();
  }
 }
}

但我的问题(或澄清)是关于他对这种方法所说的话。。。他提到

The trick is that rather than having the main program repeatedly ask each ReturnDigest thread whether it’s finished, you let the thread tell the main program when it’s finished

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

因此,当任务完成时(而不是主线程轮询),主线程不会收到通知。这是因为主线程不关心结果。它一直走到尽头,然后就结束了。新线程将在完成后运行另一个计算

我理解正确吗

这与调试有什么关系?线程现在是否成为主线程?调试器现在会这样对待它吗

是否有另一种方法将结果传递回主线程

我将感谢任何有助于更好地理解这一点的帮助:)


共 (3) 个答案

  1. # 1 楼答案

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

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

  2. # 2 楼答案

    其他线程都不会成为“主线程”。主线程是以main()方法开始的线程。它的工作是启动其他线程。。。然后它就死了

    此时,您永远不会返回到主线程,但子线程有回调。。。这意味着当它们完成时,它们知道在哪里重定向程序流

    这就是你的receiveDigest()方法。它的任务是在子线程完成后显示它们的结果。这个方法是从主线程还是从子线程调用的?你觉得怎么样

    可以将结果传回主线程。要做到这一点,您需要防止主线程终止,因此它需要有一个循环使其无限期地运行,并且要防止该循环消耗处理器的任务,需要在其他线程工作时将其置于睡眠状态

    您可以在此处阅读fork and join架构的示例:

    https://www.tutorialspoint.com/java_concurrency/concurrency_fork_join.htm

  3. # 3 楼答案

    这本书误导了你

    首先,示例中没有Callback。只有一个函数按名称调用另一个函数。真正的回调是不同软件模块之间通信的一种方式。它是指向函数或对象的指针或引用,带有模块a提供给模块B的方法,以便模块B在发生有趣的事情时调用它。它与线程没有任何关系

    其次,所谓的回调在线程之间不进行任何通信。函数调用完全在main()线程死亡后的新线程中进行