草庐IT

java - 在 stdin 上阻塞使 Java 进程需要多 350 毫秒才能退出

coder 2023-05-17 原文

我有一个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 )使用 System.in.read 运行时,当前调用需要大约 480 毫秒才能退出。注释掉的调用大约需要 120 毫秒才能退出

我原以为一旦主线程结束,程序就会终止,但很明显,还有另外 300 毫秒的延迟(您可以通过在 Thread.sleep 之后添加 println 来看到这一点)。我试过 t.interrupt t.stop System.exit这应该“立即”停止事情,但他们似乎都不能让程序跳过它的 350ms 额外退出延迟似乎什么都不做。

任何人都知道为什么会这样,如果我能做些什么来避免这种延迟?

最佳答案

事实证明,除了收集坏消息外,还有一些解决方法,请参阅这篇文章的最底部。

这不是一个完整的答复,只是我脑海中出现了一个带有套接字的支票。
基于此和System.in.read()我也复制了一个实验,延迟可能是向操作系统发出未完成的同步 I/O 请求的代价。 (编辑:实际上这是一个显式等待,当 VM 关闭时线程未正常退出时启动,请参见水平线下方)

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

  • 如果您创建绑定(bind)套接字 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()不在外部调用。这也有额外的等待

  • 因此,拥有套接字(只是绑定(bind)或什至连接)不是问题,但是等待某些事情发生( 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 srv , int port , Socket s1,s2 ,在Java端杀东西更快:close()srv/s1/s2关闭 accept()read()调用非常快,并且用于关闭 accept()特别是,new Socket("localhost",port)也可以工作(只是在实际连接同时到达时它具有竞争条件)。也可以使用 close() 关闭连接尝试,为此只需要一个对象(因此必须使用 s1=new Socket();s1.connect(new InetSocketAddress("localhost",srv.getLocalPort())); 而不是连接构造函数)。

    TL;DR:这对你来说重要吗?完全没有:我试过 System.in.close();并且它对System.in.read();绝对没有影响.

    新的坏消息。当线程处于 native 代码中,并且该 native 代码不检查“安全点”时,这是关闭过程的最后一步 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? ),它可能无论如何都行不通。

    图书馆方面,readSingle ( https://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 ,这只是前面提到的 wrapper restartable_read )

    所以,这是设计使然。

    有效的一件事是提供您自己的 System.in (尽管是 final 有一个 setIn() 用于此目的的方法,在 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 cooking 之间取得平衡。 Thread.sleep()正确检查是否被关闭,因此即使您将其设置为 10000,该过程也会快速退出。

    关于java - 在 stdin 上阻塞使 Java 进程需要多 350 毫秒才能退出,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48951611/

    有关java - 在 stdin 上阻塞使 Java 进程需要多 350 毫秒才能退出的更多相关文章

    1. ruby - 我需要将 Bundler 本身添加到 Gemfile 中吗? - 2

      当我使用Bundler时,是否需要在我的Gemfile中将其列为依赖项?毕竟,我的代码中有些地方需要它。例如,当我进行Bundler设置时:require"bundler/setup" 最佳答案 没有。您可以尝试,但首先您必须用鞋带将自己抬离地面。 关于ruby-我需要将Bundler本身添加到Gemfile中吗?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/4758609/

    2. ruby - 在 jRuby 中使用 'fork' 生成进程的替代方案? - 2

      在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',

    3. ruby - rspec 需要 .rspec 文件中的 spec_helper - 2

      我注意到像bundler这样的项目在每个specfile中执行requirespec_helper我还注意到rspec使用选项--require,它允许您在引导rspec时要求一个文件。您还可以将其添加到.rspec文件中,因此只要您运行不带参数的rspec就会添加它。使用上述方法有什么缺点可以解释为什么像bundler这样的项目选择在每个规范文件中都需要spec_helper吗? 最佳答案 我不在Bundler上工作,所以我不能直接谈论他们的做法。并非所有项目都checkin.rspec文件。原因是这个文件,通常按照当前的惯例,只

    4. ruby - 通过 ruby​​ 进程共享变量 - 2

      我正在编写一个gem,我必须在其中fork两个启动两个webrick服务器的进程。我想通过基类的类方法启动这个服务器,因为应该只有这两个服务器在运行,而不是多个。在运行时,我想调用这两个服务器上的一些方法来更改变量。我的问题是,我无法通过基类的类方法访问fork的实例变量。此外,我不能在我的基类中使用线程,因为在幕后我正在使用另一个不是线程安全的库。所以我必须将每个服务器派生到它自己的进程。我用类变量试过了,比如@@server。但是当我试图通过基类访问这个变量时,它是nil。我读到在Ruby中不可能在分支之间共享类变量,对吗?那么,还有其他解决办法吗?我考虑过使用单例,但我不确定这是

    5. ruby - 如何在 Lion 上安装 Xcode 4.6,需要用 RVM 升级 ruby - 2

      我实际上是在尝试使用RVM在我的OSX10.7.5上更新ruby,并在输入以下命令后:rvminstallruby我得到了以下回复:Searchingforbinaryrubies,thismighttakesometime.Checkingrequirementsforosx.Installingrequirementsforosx.Updatingsystem.......Errorrunning'requirements_osx_brew_update_systemruby-2.0.0-p247',pleaseread/Users/username/.rvm/log/138121

    6. java - 等价于 Java 中的 Ruby Hash - 2

      我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

    7. java - 从 JRuby 调用 Java 类的问题 - 2

      我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

    8. java - 我的模型类或其他类中应该有逻辑吗 - 2

      我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我

    9. ruby - 为什么在 ruby​​ 中创建 Rational 不需要新方法 - 2

      这个问题在这里已经有了答案:关闭10年前。PossibleDuplicate:Rubysyntaxquestion:Rational(a,b)andRational.new!(a,b)我正在阅读ruby镐书,我对创建有理数的语法感到困惑。Rational(3,4)*Rational(1,2)产生=>3/8为什么Rational不需要new方法(我还注意到例如我可以在没有new方法的情况下创建字符串)?

    10. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

      什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

    随机推荐