有 Java 编程相关的问题?

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

java如何中止阻塞进程。getInputStream()。用close()读取()吗?

我正试图中止一个已经阻塞的输入流。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()。销毁()

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

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


共 (5) 个答案

  1. # 1 楼答案

    你试过close()但没用。我唯一能想到的另一件事是在被阻止的线程上调用Thread.interrupt()。。。但我怀疑这也行得通

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

  2. # 2 楼答案

    我想我不能在Java1.7中确定,但我在Java1.6中做到了这一点。你需要3个线程:

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

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

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

  3. # 3 楼答案

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

    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. # 4 楼答案

    我看到过类似的情况,在我的例子中,每次java线程阻塞时,都是因为子进程将文件描述符提供给了它自己的子进程,在调用Process.destroy()后,该子进程仍处于活动状态。所以,示意图上是这样的:

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

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

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

    再想一想,如果我错了,那就去处理。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
    
  5. # 5 楼答案

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

    免责声明:我是NuProcess的作者