1

我试图通过关闭流来中止已经阻塞的 InputStream.read() ,但 read() 不返回。

public static void main(String args[]) throws Exception
{
    ProcessBuilder builder = new ProcessBuilder("sleep", "100000");
    Process process = builder.start();
    final InputStream is = process.getInputStream();

    Thread worker = new Thread() {
        public void run() {
            try {
                int data;
                while ((data = is.read()) != -1) {
                    System.out.println("Read:" + data);
                }
            } catch(IOException e) {
                e.printStackTrace();
            }
        }
    };

    worker.start();
    Thread.sleep(2000);
    // process.destroy(); 
    is.close();
    worker.join();
}

有没有办法通过关闭流来中止已经阻塞的 read(),而不调用 process.destroy()?

这个问题的背景是一个复杂的eclipse插件,大多数时候会通过调用process.destroy()中止阻塞的BufferedReader.readLine(),但有时readLine()不会中止。我怀疑 JVM 内部 ProcessReaper 线程存在一些竞争,我正试图通过显式关闭流来解决这个问题。

我正在使用 Linux 和 JDK 1.7.0_25-b15。

4

5 回答 5

2

老问题,新答案。NuProcess库为进程实现了非阻塞 I/O,因此您可以完全避免这种情况。

免责声明:我是 NuProcess 的作者。

于 2013-11-20T03:26:55.867 回答
1

你试过close()了,它不起作用。我能想到的唯一另一件事是调用Thread.interrupt()被阻塞的线程……但我怀疑这也行得通。

真正的问题是规范(即各自的javadocs)并没有说明这些东西是否会起作用。即使它们确实可以工作……对于某些操作系统平台上的某些 Java 版本……也不能保证它们可以在其他版本/平台组合上工作。

于 2013-11-11T15:17:58.207 回答
1

我见过类似的东西,在我的例子中,每次java线程阻塞时,都是因为子进程将文件描述符提供给了它自己的子进程,在Process.destroy()被调用后它仍然活着。因此,示意性地看起来像这样:

java thread --> child process --> grandchild process

我实际上确实去检查了Process.destroy()Windows 和 Linux 平台的源代码,结果发现执行了一个kill 9命令。如果要杀孙子,kill -9就需要命令。我不知道在不使用 JNI 的情况下解决此问题的任何好方法,但是由于您是为 Eclipse 编写的,因此您可以尝试使用CDT Spawner类而不是java.lang.Runtime. 我记得它提供了运行时之外的一些功能,但不记得它是否可以解决这个问题。例如,此类用于通过 Eclipse CDT 环境启动和调试本机进程。

错误JDK-4770092解释了为什么在 Windows 上很难杀死孙子进程。据我了解,Sun/Oracle 决定提供最低公分母,这就是为什么在 Unix 上也没有孙辈被杀的原因。

再想一想,如果我错了并且 Process.destroy() 发送了一个 SIGTERM,那么下面的 shell 脚本应该将所有子进程与 java 隔离,并且可以在 Linux 和 Windows 上的 Cygwin 上运行。

#!/bin/sh

# Initialize variables
program="$1"; shift
child_pid=0

# Register a signal handler procedure
signalHandler() {
    echo "Sending SIGKILL to process $child_pid and all its children"
    [0 = $child_pid] || kill -9 -$child_pid
    echo "Removing temporary folder: $tmp_folder"
    rm -fr "$tmp_folder"
}
trap signalHandler 0 1 2 3 15

# Create a temporary folder
tmp_folder=$(mktemp -d --quiet)

# Launch a child process in background
program "$@" >$(tmp_folder)/somefile 2>&1 &

# Remember the subprocess id
child_pid=$!

# Make tail exit when the subprocess exits
# Maybe, this is a bad idea. I haven’t tested it
# If it doesn't work, one can read from file with Java NIO
tail --follow --pid=$child_pid $(tmp_folder)/somefile
于 2013-11-25T08:04:23.837 回答
1

我有一个类似的问题,我通过在从流中读取之前检查一些可用字节来解决。修改您的示例:

public static void main(String args[]) throws Exception
{
    ProcessBuilder builder = new ProcessBuilder("sleep", "100000");
    Process process = builder.start();
    final InputStream is = process.getInputStream();
    final AtomicBoolean stopReading = new AtomicBoolean(false);
    final long CHECK_INTERVAL = 100; // msec.

    Thread worker = new Thread() {
        public void run() {
            try {
                workerLoop:
                while (true) {
                    int availableBytes = is.available(); // this call should be non-blocking
                    if (availableBytes > 0) {
                        byte[] buffer = new byte[availableBytes];
                        int c = is.read(buffer); // this call should be non-blocking
                        for (int i = 0; i < c; i++) {
                            if (buffer[i] == -1) // standard check for the end of stream
                            {
                                break workerLoop;
                            }
                            // do something with the data....
                        }                        
                    } else if (stopReading.get()) {
                        break;
                    } else {
                        Thread.sleep(CHECK_INTERVAL);
                    }
                }
            } catch(Exception e) {
                e.printStackTrace();
            }
        }
    };

    worker.start();
    Thread.sleep(2000);
    stopReading.set(true); // request to terminate the thread
    worker.join();
    is.close();
} 

可以修改代码以中断工作线程,而不是使用布尔标志。

我正在使用 Windows 10 和 Oracle Java 8。

于 2019-12-08T04:52:09.707 回答
-1

我想在 Java 1.7 中我不能肯定地说,但我在 Java 1.6 中已经做到了。你需要3个线程:

  • 读取流的线程
  • 一个监控线程,用于监控读取线程
  • 主线程(为您自动创建,但我只是想确保我们不会忘记它。)

读取线程应该更新它和监视器线程之间共享的引用,但更重要的是,监视器需要对读取线程有引用。 读取线程在每次读取时更新时间值。监视器会休眠一段设定的时间,每次唤醒时,它都会检查该时间戳。如果前一时间和当前时间之间的差值超过某个值,则监视器会在读取线程上强制中断,然后也会自行停止。

可以想象,这可以只用两个线程来完成——Main 和 Monitor,主线程将在其中读取并执行monitor.setReaderThread(Thread.currentThread()); monitorThread.start(). 但我只用三个线程完成了这个。

于 2013-11-11T17:05:09.713 回答