Ответ 1
Как оказалось, помимо сбора плохих новостей, есть и обходное решение, см. Самую нижнюю часть этой публикации.
Это не полный ответ, просто в голову вошел чек, с сокетами.
Основываясь на этом и эксперименте System.in.read()
я воспроизвел тоже, задержка может быть связана с тем, что имеет выдающийся синхронный запрос ввода-вывода в ОС. (Edit: на самом деле это явное ожидание, которое срабатывает, когда потоки не выходят нормально, когда 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(...);
line, если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 srv
, int port
, Socket s1,s2
, быстрее убивать вещи на стороне Java: close()
on srv
/s1
/s2
shuts down 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();
,
Новые плохие новости. Когда поток находится в собственном коде и этот собственный код не проверяет "safepoint", один из последних шагов процедуры выключения ожидает 300 миллисекунд, минимум:
// [...] In theory, we // don't have to wait for user threads to be quiescent, but it 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
on /proc/self/fd/0
Хотя read
(и recv
тоже) завернуто в какую-нибудь магическую вещь RESTARTABLE
(https://github.com/openjdk-mirror/jdk7u-hotspot/blob/master/src/os/linux/vm/os_linux.inline.hpp# L168 и read
немного ниже - это оболочка для 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 (например, как прервать вызов fread?), Это может не сработать.
На стороне библиотеки 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()
вы можете балансировать между тем, чтобы быть невосприимчивым или готовить с процессором. Thread.sleep()
корректно проверяет, закрыт ли он, поэтому даже если вы поставите его до 10000, процесс будет быстро завершен.