在stdin上阻塞使得Java进程需要350ms才能退出

时间:2018-02-23 15:49:38

标签: java

我有一个Java程序如下:

public class foo{

  public static void main(String[] args) throws Exception{
    Thread t = new Thread(
      new Runnable() {
        public void run() {
          try{System.in.read();}catch(Exception e){}
        }
      }
    );
    t.setDaemon(true);
    t.start();
    Thread.sleep(10); // Make sure it hits the read() call
    t.interrupt();
    t.stop();
    System.exit(0);
  }
}

使用time java foo调用运行此{System.in.read}需要大约480毫秒才能退出,同时运行System.in.read调用注释需要大约120毫秒退出

我曾经想过,一旦主线程到达终点,程序就会终止,但很明显,还有另外300ms的滞后(你可以通过在Thread.sleep之后添加一个println来看到这一点)。我尝试了t.interrupt t.stop System.exit这应该立即停止事情"但是他们似乎都没有能够让程序跳过350毫秒的额外退出延迟看似无所事事。

任何人都知道为什么会这样,如果我有什么办法可以避免这种延迟?

2 个答案:

答案 0 :(得分:18)

除了收集坏消息之外,还有一些解决方法,请参阅本文的最后部分。

这不是一个完整的回复,只是一个支票弹出我的脑海,插座。
基于此和我已经复制的System.in.read()实验,延迟可能是对操作系统提供出色的同步I / O请求的成本。 (编辑:实际上它是一个明确的等待,当线程在VM关闭时没有正常退出时启动,见水平线下方)

(我用while(true);结束线程,所以它(它们)永远不会过早退出)

  • 如果您创建了一个绑定套接字final ServerSocket srv=new ServerSocket(0);,则退出仍然是正常的'
  • 如果你srv.accept();,你突然有额外的等待
  • 如果你创造一个"内部"守护程序线程Socket s=new Socket("localhost",srv.getLocalPort());Socket s=srv.accept();外部,它变为“正常”'再次
  • 但是如果您在其中任何一个上调用s.getInputStream().read();,那么您还有额外的等待
  • 如果你用两个套接字进行,额外的等待时间会延长一点(远低于300,但对我来说一致20-50毫秒)
  • 如果没有在外部调用new Socket(...);
  • 拥有内部线程,也可能卡在accept()行上。这也有额外的等待

所以有套接字(只是绑定甚至连接)不是问题,但等待某事发生(accept()read())会引入一些东西。

代码(此变体命中两个s.getInputSteam().read() - s)

import java.net.*;
public class foo{
  public static void main(String[] args) throws Exception{
    Thread t = new Thread(
      new Runnable() {
        public void run() {
          try{
            final ServerSocket srv=new ServerSocket(0);

            Thread t=new Thread(new Runnable(){
              public void run(){
                try{
                  Socket s=new Socket("localhost",srv.getLocalPort());
                  s.getInputStream().read();

                  while(true);
                }catch(Exception ex){}
            }});
            t.setDaemon(true);
            t.start();

            Socket s=srv.accept();

            s.getInputStream().read();

            while(true);
          }catch(Exception ex){}
        }
      }
    );
    t.setDaemon(true);
    t.start();
    Thread.sleep(1000);
  }
}

我还尝试了评论中显示的内容:访问{我刚刚使用static)到ServerSocket srvint portSocket s1,s2,杀死内容的速度更快Java端:close() / srv / s1上的s2关闭accept()read()非常快,并关闭accept() 1}}特别是,new Socket("localhost",port)也可以工作(当实际连接同时到达时,它就具有竞争条件)。使用close()也可以关闭连接尝试,只需要一个对象(因此必须使用s1=new Socket();s1.connect(new InetSocketAddress("localhost",srv.getLocalPort()));而不是连接构造函数。)

TL; DR:对你来说重要吗?完全没有:我尝试了System.in.close();,它对System.in.read();完全没有影响。

新的坏消息。当一个线程使用本机代码,并且该本机代码不检查' safepoint'时,关闭过程waits for 300 milliseconds的最后一步,最少:

// [...] In theory, we
// don't have to wait for user threads to be quiescent, but it's always
// better to terminate VM when current thread is the only active thread, so
// wait for user threads too. Numbers are in 10 milliseconds.
int max_wait_user_thread = 30;                  // at least 300 milliseconds

它正在等待,因为线程正在fread上执行一个简单的/proc/self/fd/0

虽然read(以及recv也包含在一些神奇的RESTARTABLE循环内容中https://github.com/openjdk-mirror/jdk7u-hotspot/blob/master/src/os/linux/vm/os_linux.inline.hpp#L168read稍微低一点 - 但它是一个另一个文件中fread的包装器),似乎知道EINTR

#define RESTARTABLE(_cmd, _result) do { \
    _result = _cmd; \
  } while(((int)_result == OS_ERR) && (errno == EINTR))

[...]

inline size_t os::restartable_read(int fd, void *buf, unsigned int nBytes) {
  size_t res;
  RESTARTABLE( (size_t) ::read(fd, buf, (size_t) nBytes), res);
  return res;
}

,但这并没有发生在任何地方,此外还有一些评论,他们不想干扰libpthread自己的信令和处理程序。根据SO上的一些问题(如How to interrupt a fread call?),它可能无论如何都不起作用。

在图书馆方面,readSinglehttps://github.com/openjdk-mirror/jdk7u-jdk/blob/master/src/share/native/java/io/io_util.c#L38)是已被调用的方法:

jint
readSingle(JNIEnv *env, jobject this, jfieldID fid) {
    jint nread;
    char ret;
    FD fd = GET_FD(this, fid);
    if (fd == -1) {
        JNU_ThrowIOException(env, "Stream Closed");
        return -1;
    }
    nread = (jint)IO_Read(fd, &ret, 1);
    if (nread == 0) { /* EOF */
        return -1;
    } else if (nread == JVM_IO_ERR) { /* error */
        JNU_ThrowIOExceptionWithLastError(env, "Read error");
    } else if (nread == JVM_IO_INTR) {
        JNU_ThrowByName(env, "java/io/InterruptedIOException", NULL);
    }
    return ret & 0xFF;
}

能够处理被打断的'在JRE级别上,但由于fread未返回(因为所有非Windows,IO_Read#define - d为JVM_Read,因此该部分无法执行,这只是前面提到的restartable_read的{​​{3}}

所以,它是设计的。

有一件事是提供你自己的System.in(尽管finalsetIn()方法用于此目的,在JNI中进行非标准交换)。但它涉及民意调查,所以有点难看:

import java.io.*;
public class foo{
  public static void main(String[] args) throws Exception{

    System.setIn(new InputStream() {
      InputStream in=System.in;
      @Override
      public int read() throws IOException {
        while(in.available()==0)try{Thread.sleep(100);}catch(Exception ex){}
        return in.read();
      }
    });

    Thread t = new Thread(
      new Runnable() {
        public void run() {
          try{
            System.out.println(System.in.read());
            while(true);
          }catch(Exception ex){}
        }
      }
    );
    t.setDaemon(true);
    t.start();
    Thread.sleep(1000);
  }
}

Thread.sleep()位于InputStream.read()内,您可以在无响应或使用CPU烹饪之间取得平衡。 Thread.sleep()正确检查是否已关闭,因此即使您将其设置为10000,该过程也会快速退出。

答案 1 :(得分:8)

正如其他人所评论的那样,线程中断不会导致阻塞I / O调用立即停止。等待正在发生,因为系统正在等待来自File(stdin)的输入。如果您要为程序提供一些初始输入,您会发现它完成得更快:

$ time java -cp build/libs/testjava.jar Foo

real    0m0.395s
user    0m0.040s
sys     0m0.008s
$ time java -cp build/libs/testjava.jar Foo <<< test

real    0m0.064s
user    0m0.036s
sys     0m0.012s

如果您想避免等待I / O线程,那么您可以先检查可用性。然后,您可以使用可中断的方法执行等待,例如Thread.sleep

while (true) {
    try {
        if (System.in.available() > 0) {
            System.in.read();
        }
        Thread.sleep(400);
    }
    catch(Exception e) {

    }
}

此程序每次都会快速退出:

$ time java -cp build/libs/testjava.jar Foo

real    0m0.065s
user    0m0.040s
sys     0m0.008s
$ time java -cp build/libs/testjava.jar Foo <<< test

real    0m0.065s
user    0m0.040s
sys     0m0.008s