【发布时间】:2012-02-16 10:58:56
【问题描述】:
共有三个 Java 应用程序,appB 只是启动 appC,它将永远运行。 appA 启动 appB 并读取其输出。 但 appA 永远不会退出。任何人都知道为什么会发生这种情况以及如何解决它。 如果我不读取 appA 中的流,它不会挂起。
我尝试了两种方法:
- 直接读取输出流(readViaInputStream)或BufferedReader(readViaBufferedReader)。
它们不起作用,输出将是:
// 如果我们调用它们,主应用程序挂起,输出将是:
//调用子进程之前
//调用子进程后
//主程序挂在这里。
// 从不输出 - “读取流完成。”
在 readViaInputStream 中,它在 BufferedInputStream.fill() 方法中的 fill() 方法处挂起 int n = getInIfOpen().read(buffer, pos, buffer.length - pos);它调用 FileInputStream 类的原生方法。
与 readViaBufferedReader 方法相同。
- 使用另一个线程读取输出流。
它也不起作用,输出将是:
//调用子进程之前
//调用子进程后
// 读取流完成。
// ===> 主程序挂起
非常感谢您的回复:)
代码如下 - 更新为使用下一条评论中提供的代码 Guillaume Polet。
public class MainApp {
public static enum APP {
B, C;
}
public static void main(String[] args) throws Exception,
InterruptedException {
if (args.length > 0) {
APP app = APP.valueOf(args[0]);
switch (app) {
case B:
performB();
break;
case C:
performC();
break;
}
return;
}
performA();
}
private static void performA() throws Exception {
String javaBin = "java";
String[] cmdArray = { javaBin, "-cp",
System.getProperty("java.class.path"), MainApp.class.getName(),
APP.B.name() };
ProcessBuilder builder = new ProcessBuilder(cmdArray);
builder.redirectErrorStream(true);
final Process process = builder.start();
process.getOutputStream().close();
process.getErrorStream().close();
// if we call this, the main app hangs, the output would be:
// Before call child process
// After call child process
// the main program hangs here.
// Never output - "Read stream finished."
readViaInputStream(process);
// if we call this, the main app hangs, the output would be:
// Before call child process
// After call child process
// the main program hangs here.
// Never output - "Read stream finished."
// readViaBufferedReader(process);
// if we call this, the main app still hangs, the output would be:
// Before call child process
// After call child process
// Read stream finished.
// ===> and the main program hang
// readOutputViaAnotherThread(process);
System.err.println("Read stream finished."); // never come here
}
private static void performB() throws Exception {
System.out.println("Before call child process");
String javaBin = "java";
String[] cmdArray = { javaBin, "-cp",
System.getProperty("java.class.path"), MainApp.class.getName(),
APP.C.name() };
ProcessBuilder builder = new ProcessBuilder(cmdArray);
Process process = builder.start();
process.getInputStream().close();
process.getOutputStream().close();
process.getErrorStream().close();
System.out.println("After call child process");
System.exit(0); // no difference with or without this line.
}
private static void performC() throws Exception {
Thread thread = new Thread() {
@Override
public void run() {
int i = 0;
while (true) {
try {
Thread.sleep(60 * 2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.err.println("child " + ++i);
}
}
};
thread.start();
thread.join();
}
private static void readViaInputStream(final Process process)
throws Exception {
// System.err.println("exitValue: " + process.waitFor());
InputStream is = process.getInputStream();
int result;
while ((result = is.read()) != -1) {
System.err.println(result);
}
}
private static void readViaBufferedReader(final Process process)
throws Exception {
BufferedReader in = new BufferedReader(new InputStreamReader(
process.getInputStream(), "utf-8"));
String result = "";
while ((result = in.readLine()) != null) {
System.err.println(result);
}
}
private static void readOutputViaAnotherThread(final Process process)
throws Exception {
class ReadOutputStreamThread extends Thread {
public void run() {
running = true;
try {
BufferedReader in = new BufferedReader(
new InputStreamReader(process.getInputStream(),
"utf-8"));
String result = "";
while (running && (result = in.readLine()) != null) {
System.err.println(result);
}
} catch (Exception e) {
e.printStackTrace();
}
};
private volatile boolean running;
public void shutdown() throws IOException {
running = false;
// this has no impact
process.getInputStream().close();
interrupt();
}
}
ReadOutputStreamThread readOutputThread = new ReadOutputStreamThread();
// if we set this readOutputThread as daemon, it works, but the thread
// will remains run forever.
// readOutputThread.setDaemon(true);
readOutputThread.start();
System.err.println("exitValue: " + process.waitFor());
readOutputThread.shutdown();
}
}
【问题讨论】:
-
你读过thread.join的javadoc吗?
-
是的,子进程主线程似乎永远在等待。因为生成的线程不是守护进程,所以不需要加入调用。根据问题,问题不在于appC,而在于appA,它永远从输入流中读取。发生这种情况是因为流的另一部分没有关闭它。
-
非常感谢您的回复。我提供的代码来自真实项目,进程 C 意味着永远运行。 - 唯一的区别是在我的真实项目中,一个 C# 应用程序正在启动应用程序 B 并读取其输出相同的问题。调试jdk源码后,貌似挂在BufferedReader.readLine(),调用BufferedReader中的fill()方法,最后调用sun.nio.cs.StreamDecoder.read()方法。 private void fill() throws IOException { do {n = in.read(cb, dst, cb.length - dst); // 它挂在这里 } while (n == 0);} 我不确定这是否是 JDK 错误。