Поймите, как сделать Non-blocking IO?
Я использую Undertow для создания простого приложения.
public class App {
public static void main(String[] args) {
Undertow server = Undertow.builder().addListener(8080, "localhost")
.setHandler(new HttpHandler() {
public void handleRequest(HttpServerExchange exchange) throws Exception {
Thread.sleep(5000);
exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
exchange.getResponseSender().send("Hello World");
}
}).build();
server.start();
}
}
Я открываю вкладку браузера на localhost:8080
, и я открываю вторую
вкладку также на localhost:8080
В этот раз первая вкладка будет ждать 5 секунд, а вторая будет ждать 10 секунд
Почему это так?
Ответы
Ответ 1
HttpHandler выполняется в потоке ввода-вывода. Как указано в документации:
IO-потоки выполняют задачи без блокировки, а никогда не должны выполнять операции блокировки, потому что они несут ответственность за несколько соединений, поэтому, когда операция блокирует другие соединения, они будут зависать. Один IO-поток на ядре процессора является разумным по умолчанию.
В документах жизненного цикла запроса обсуждается, как отправить запрос в рабочий поток:
import io.undertow.Undertow;
import io.undertow.server.*;
import io.undertow.util.Headers;
public class Under {
public static void main(String[] args) {
Undertow server = Undertow.builder()
.addListener(8080, "localhost")
.setHandler(new HttpHandler() {
public void handleRequest(HttpServerExchange exchange)
throws Exception {
if (exchange.isInIoThread()) {
exchange.dispatch(this);
return;
}
exchange.getResponseHeaders()
.put(Headers.CONTENT_TYPE, "text/plain");
exchange.getResponseSender()
.send("Hello World");
}
})
.build();
server.start();
}
}
Я заметил, что вы не обязательно получите один рабочий поток для каждого запроса - когда я установил точку останова на заголовке, я получил один поток на каждого клиента. Есть пробелы как в Undertow, так и в базовых документах XNIO, поэтому я не уверен, что это за намерение.
Ответ 2
Undertow использует NIO, что означает, что один поток обрабатывает все запросы. Если вы хотите выполнять блокирующие операции в обработчике запросов, вам нужно отправить эту операцию в рабочий поток.
В вашем примере вы помещаете поток в режим сна, что означает, что любая обработка запросов укладывается в спящий режим, так как этот поток обрабатывает все запросы.
Однако, даже если вы отправили операцию в рабочий поток и поставили ее на спящий режим, вы все равно увидите проблему блокировки, которую вы упомянули. Это связано с тем, что вы открываете один и тот же URL-адрес на нескольких вкладках в одном браузере. У браузеров есть внутренняя блокировка. Если вы откроете тот же URL-адрес на разных вкладках, второй URL-адрес запустит запрос после завершения первого. Попробуйте любой URL, который вы хотите увидеть сами. Вы можете легко смутить это поведение браузера.
Ответ 3
Проще всего сделать, чтобы обернуть обработчик в BlockingHandler.
import io.undertow.Undertow;
import io.undertow.server.*;
import io.undertow.server.handlers.BlockingHandler;
import io.undertow.util.Headers;
public class Under {
public static void main(String[] args) {
Undertow server = Undertow.builder()
.addHttpListener(8080, "localhost")
.setHandler(new BlockingHandler(new HttpHandler() {
public void handleRequest(HttpServerExchange exchange)
throws Exception {
exchange.getResponseHeaders()
.put(Headers.CONTENT_TYPE, "text/plain");
exchange.getResponseSender()
.send("Hello World");
}
})).build();
server.start();
}
}