【问题标题】:How to interrupt reading on System.in?如何中断对 System.in 的阅读?
【发布时间】:2018-09-06 07:39:55
【问题描述】:

如果我从System.in 开始读取,它将阻塞线程直到它获取数据。没有办法阻止它。以下是我尝试过的所有方法:

  • 中断线程
  • 停止线程
  • 关闭System.in
  • 调用System.exit(0) 确实会停止线程,但它也会杀死我的应用程序,所以并不理想。
  • 在控制台中输入 char 会使方法返回,但我不能依赖用户输入。

无效的示例代码:

public static void main(String[] args) throws InterruptedException {
    Thread th = new Thread(() -> {
        try {
            System.in.read();
        } catch (IOException e) {
            e.printStackTrace();
        }
    });
    th.start();
    Thread.sleep(1000);
    System.in.close();
    Thread.sleep(1000);
    th.interrupt();
    Thread.sleep(1000);
    th.stop();
    Thread.sleep(1000);
    System.out.println(th.isAlive()); // Outputs true
}

当我运行这段代码时,它会输出true 并永远运行。

如何以可中断的方式读取System.in

【问题讨论】:

  • System.in.close()
  • @JBNizet 你真的认为我没有尝试过吗?
  • 应该是th.close(); 否?
  • @piegames 是的,我确实这么认为。你?结果如何?
  • 没用,很遗憾。

标签: java multithreading inputstream system.in


【解决方案1】:

您应该设计 run 方法,以便它可以自行确定何时终止。在线程上调用 stop() 或类似方法将是 inherently unsafe

但是,仍然存在如何避免 System.in.read 内部阻塞的问题?为此,您可以轮询 System.in.available 直到它在读取之前返回 > 0。

示例代码:

    Thread th = new Thread(() -> {
        try {
            while(System.in.available() < 1) {
                Thread.sleep(200);
            }
            System.in.read();
        } catch (InterruptedException e) {
            // sleep interrupted
        } catch (IOException e) {
            e.printStackTrace();
        }
    });

当然,通常认为使用阻塞 IO 方法而不是轮询是有利的。但是轮询确实有它的用途。在您的情况下,它允许该线程干净地退出。

更好的方法:

避免轮询的better approach 将重构代码,以便您打算杀死的任何线程都不允许直接访问System.in。这是因为 System.in 是不应关闭的 InputStream。相反,主线程或另一个专用线程将从 System.in 读取(阻塞),然后将任何内容写入缓冲区。反过来,该缓冲区将由您打算杀死的线程监视。

示例代码:

public static void main(String[] args) throws InterruptedException, IOException {
    PipedOutputStream stagingPipe = new PipedOutputStream();
    PipedInputStream releasingPipe = new PipedInputStream(stagingPipe);
    Thread stagingThread = new Thread(() -> {
        try {
            while(true) {
                stagingPipe.write(System.in.read());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    });     
    stagingThread.setDaemon(true);
    stagingThread.start();
    Thread th = new Thread(() -> {
        try {
            releasingPipe.read();
        } catch (InterruptedIOException e) {
            // read interrupted
        } catch (IOException e) {
            e.printStackTrace();
        }
    });
    th.start();
    Thread.sleep(1000);
    Thread.sleep(1000);
    th.interrupt();
    Thread.sleep(1000);
    Thread.sleep(1000);
    System.out.println(th.isAlive()); // Outputs false
}       

但是等等! (另一个 Java API 失败)

不幸的是,正如用户Motowski 所指出的,PipedInputSteam 的 Java API 实现中存在一个“无法修复”的错误。所以如果使用如上图未修改的库版本PipedInputSteam,有时会通过wait(1000)触发长时间休眠。要解决该错误,开发人员必须创建自己的 FastPipedInputStream 子类,如 here 所述。

【讨论】:

  • 我正在尝试使用 System.in.available() 制作一个包装器 InputStream,但它似乎不适用于 Scanner。
  • 使线程成为守护进程而不是使用System.exit() 杀死应用程序,它应该可以工作
  • PipedInputStream 是通过轮询实现的。他们使用 wait(1000) 循环。不知道为什么,刚查了一下JDK 1.8的源码。
  • @MostowskiCollapse 看来你是对的。另一个 Java API 失败了!开发人员必须创建自己的 FastPipedInputStream 子类。 stackoverflow.com/questions/28617175/…
【解决方案2】:

我编写了一个允许被中断的包装器 InputStream 类:

package de.piegames.voicepi.stt;
import java.io.IOException;
import java.io.InputStream;

public class InterruptibleInputStream extends InputStream {

    protected final InputStream in;

    public InterruptibleInputStream(InputStream in) {
        this.in = in;
    }

    /**
     * This will read one byte, blocking if needed. If the thread is interrupted while reading, it will stop and throw
     * an {@link IOException}.
     */     
    @Override
    public int read() throws IOException {
        while (!Thread.interrupted())
            if (in.available() > 0)
                return in.read();
            else
                Thread.yield();
        throw new IOException("Thread interrupted while reading");
    }

    /**
     * This will read multiple bytes into a buffer. While reading the first byte it will block and wait in an
     * interruptable way until one is available. For the remaining bytes, it will stop reading when none are available
     * anymore. If the thread is interrupted, it will return -1.
     */
    @Override
    public int read(byte b[], int off, int len) throws IOException {
        if (b == null) {
            throw new NullPointerException();
        } else if (off < 0 || len < 0 || len > b.length - off) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return 0;
        }
        int c = -1;
        while (!Thread.interrupted())
            if (in.available() > 0) {
                c = in.read();
                break;
            } else
                Thread.yield();
        if (c == -1) {
            return -1;
        }
        b[off] = (byte) c;

        int i = 1;
        try {
            for (; i < len; i++) {
                c = -1;
                if (in.available() > 0)
                    c = in.read();
                if (c == -1) {
                    break;
                }
                b[off + i] = (byte) c;
            }
        } catch (IOException ee) {
        }
        return i;
    }

    @Override
    public int available() throws IOException {
        return in.available();
    }

    @Override
    public void close() throws IOException {
        in.close();
    }

    @Override
    public synchronized void mark(int readlimit) {
        in.mark(readlimit);
    }

    @Override
    public synchronized void reset() throws IOException {
        in.reset();
    }

    @Override
    public boolean markSupported() {
        return in.markSupported();
    }
}

Thread.yield() 调整为休眠,只要您可以接受的最大延迟时间,并为中断时的一些异常做好准备,但除此之外它应该可以正常工作。

【讨论】:

  • 好答案,但你不应该吞下read(byte b[], int off, int len)中的IOException
  • 这可能会在 EOF 上旋转。在InputStream 中指定的available() 的合约是0 when it reaches the end of the input stream
  • 吞下的异常代码是从InputStream.read()复制过来的,是故意的。不过available()确实是个问题,感谢关注。
  • @teppic 不错。但是在阅读之前没有办法检测到EOF,对吗?这似乎是另一个 Java API 失败。
猜你喜欢
  • 1970-01-01
  • 2019-01-21
  • 2011-08-25
  • 1970-01-01
  • 2014-04-03
  • 2011-07-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多