如何使用close()中止阻塞process.getInputStream()。read()?

时间:2013-11-11 14:53:05

标签: java

我试图通过关闭流来中止已经阻塞的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();
}

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

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

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

5 个答案:

答案 0 :(得分:2)

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

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

答案 1 :(得分:1)

您尝试了close()但它不起作用。我能想到的另一件事就是在被阻止的线程上调用Thread.interrupt() ...但我怀疑这是否会起作用。

真正的问题是规格(即各自的javadocs)没有说明这些事情是否有效。即使它们确实有效......对于某些操作系统平台上的某些Java版本......也无法保证它们可以在其他版本/平台组合上运行。

答案 2 :(得分:1)

我见过类似的东西,在我看来,每当java线程阻塞时,都是因为子进程已经将文件描述符赋给了自己的子进程,该进程在{{1 }} 被称为。所以,示意性地看起来像这样:

Process.destroy()

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

Bug JDK-4770092提供了解释为什么很难在Windows上杀死孙子进程的原因。据我所知,Sun / Oracle决定提供最低的共同点,这就是为什么没有孙子在Unix上被杀的原因。

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

java.lang.Runtime

答案 3 :(得分: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。

答案 4 :(得分:-1)

我想我不能在Java 1.7中肯定地说,但我在Java 1.6中已经做到了这一点。你需要3个主题:

  • 读取流的线程
  • 监视读取线程的监视器线程
  • 主线程(自动为您创建,但我只想确保我们不要忘记它。)

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

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