Пришло время для следующей статьи из нашей серии «Реактивное программирование в Java». На этот раз мы рассмотрим блокировку ввода-вывода.

Традиционный ввод-вывод блокируется. Что такое блокирующая операция ввода-вывода?

Предположим, вы читаете файл или базу данных. Вы вызываете метод, который это делает, и он блокирует поток — тогда вы ничего не делаете, просто ждете. Например, вы вызвали readFile() и дождались его выполнения. Тема заблокирована и не продвигается — она ожидает. Но на самом деле процессор не занят.

В этом примере блокируются следующие потоки:

  • Заблокировано при чтении файла
  • Блокируется при чтении из БД
  • Заблокировано при тяжелых вычислениях
  • Заблокировано при ответе клиенту

Эта ситуация похожа на ту, с которой вы можете столкнуться в супермаркете, там четыре кассы, но система обслуживания отстает. Кассиры ничего не делают, только ждут реакции кассы на нажатие кнопки.

Что делать, если все темы заблокированы? Как решить такие проблемы в супермаркете?

Синхронный ввод-вывод

Стандартный вариант: синхронный ввод-вывод. Это не очень хорошо, так как в этом случае будут очереди к кассам.

Что можно сделать, чтобы избежать огромных очередей? Вы можете, например, открыть больше касс или создать больше тем.

Чем больше потоков, тем больше пунктов оформления заказа. Это рабочий вариант. Но нагрузка будет неравномерной.

Вы открываете больше касс (создаете больше потоков), но оказывается, что некоторые из них так и остаются бездействующими. На самом деле, это не просто простаивание — когда вы получаете несколько потоков, у вас есть дополнительное потребление ресурсов. Вам нужно больше памяти. Кроме того, процессору необходимо переключаться между потоками.

Чем больше потоков, тем чаще вам придется переключаться между ними. Вы получаете больше потоков, чем ядер. Предположим, у вас есть 4 ядра, но вы создали сотню потоков, потому что все остальные были заблокированы при чтении данных. Поэтому существует так называемое переключение контекста, чтобы разные потоки могли получать свою часть вычислительного времени.

Но этот подход имеет определенные недостатки. Переключение контекста не бесплатно. Это требует времени. Создание бесконечного количества потоков было бы хорошим вариантом в теории. Но на практике мы получаем снижение скорости и рост потребления памяти.
В Java есть разные подходы для предотвращения этого — это BlockingQueue и ThreadPool. Можно ограничить количество потоков, и тогда все остальные клиенты выстроятся в очередь. На старте у вас может быть минимальное количество потоков; затем их число увеличивается.

Посмотрите еще раз на пример с супермаркетом: если людей не слишком много, то открыты две кассы, а в час пик работают десять касс. Но вы не можете открыть больше, потому что вам придется арендовать дополнительное пространство и нанять больше людей. А это повредит бизнесу.

Теперь давайте обсудим некоторые новые тенденции, такие как самообслуживание, предзаказы и т. д. Это означает, что мы приближаемся к асинхронному подходу.

Асинхронный ввод-вывод

Асинхронный ввод-вывод уже давно используется, так как он нам нужен при использовании очень медленных инструментов ввода-вывода. И самое медленное устройство ввода-вывода, с которым вы всегда имеете дело, — это не консоль или клавиатура, а люди. Асинхронные системы, взаимодействующие с человеком, появились давно.

Блокирующие интерфейсы использовались при взаимодействии машины с человеком — например, старый интерфейс командной строки DOS. Есть еще утилиты, которые задают вопрос, потом блокируются и ничего не делают, кроме как ждут ответа пользователя. Асинхронный ввод-вывод использовался с тех пор, как начали появляться оконные интерфейсы. В настоящее время большинство интерфейсов являются асинхронными.

Как это работает?

Регистрируем callback-функцию, но не говорим: «Привет, пользователь, вводи данные, а я подожду». Звучит иначе: «Когда пользователь вводит данные, пожалуйста, вызовите эту функцию — callback». Этот подход используется в любой библиотеке пользовательского интерфейса. Но это было в JavaScript с самого начала. В 2009 году, когда JavaScript стал работать намного быстрее, умные ребята решили использовать его на сервере и создали инструмент под названием Node.js.

Оригинал статьи можно найти здесь.

Хотели бы научиться программировать на Java или улучшить свои навыки программирования на Java? Посетите наши тренинги.

Первоначально опубликовано на https://www.luxoft-training.com.