Программирование с использованием виртуальных потоков
Появление виртуальных потоков влечет за собой изменение майндсета. Программисты, которые писали параллельные приложения на Java в ее нынешнем виде, привыкли к тому, что им приходится иметь дело (сознательно или бессознательно) с присущими потокам ограничениями масштабирования.
Разработчики Java привыкли создавать объекты задач, часто основанные на Runnable или Callable, и передавать их исполнителям, опираясь на пулы потоков, которые существуют для экономии драгоценных ресурсов потоков. Что, если бы все это вдруг изменилось?
Project Loom пытается решить проблему масштабирования потоков, вводя новое понятие потока, которое дешевле существующих понятий и не сопоставляется напрямую с потоком ОС. Эта новая возможность по-прежнему выглядит и ведет себя как современные потоки, которые уже знакомы программистам Java.
Это означает, что вместо того, чтобы изучать совершенно новый стиль программирования (например, стиль передачи продолжения, подход с обещаниями/будущим или обратные вызовы), среда выполнения Project Loom сохраняет для виртуальных потоков ту же модель программирования, которую уже известна по существующим на данный момент времени потокам. Другими словами, виртуальные потоки — это просто потоки, по крайней мере, с точки зрения программиста.
Виртуальные потоки являются перерываемыми, поскольку пользовательскому коду не нужно явно передавать контроль над процессором. Точки планирования зависят от виртуального планировщика и JDK. Разработчики не должны делать никаких предположений о том, когда происходит передача управления, потому что это чисто деталь реализации..
Однако, чтобы понять, чем отличаются виртуальные потоки, стоит разобраться в основах теории ОС, которая лежит в основе планирования.
Когда ОС планирует потоки платформы, она выделяет потоку временной срез процессорного времени. По истечении этого времени генерируется аппаратное прерывание, и ядро может возобновить управление, удалить выполняющийся платформенный (пользовательский) поток и заменить его другим.
Именно благодаря этому механизму UNIX (и другие ОС) смогла реализовать распределение времени процессора между различными задачами, даже десятилетия назад, в эпоху, когда компьютеры имели только одно вычислительное ядро.
Виртуальные потоки, однако, обрабатываются иначе, чем потоки платформы. Ни один из существующих планировщиков виртуальных потоков не использует временные срезы для вытеснения виртуальных потоков.
Использование временных срезов для прерывания виртуальных потоков было бы возможно, и JVM уже способна взять на себя контроль над выполнением потоков Java. Например, она делает это в безопасных точках JVM.
Вместо этого виртуальные потоки автоматически передают (или уступают) свой несущий поток при выполнении блокирующего вызова (например, ввода/вывода). Эта операция выполняется библиотекой и временем выполнения и не находится под явным контролем программиста.
Таким образом, вместо того чтобы заставлять программистов явно управлять выходом или полагаться на сложности неблокирующих или операций на основе обратных вызовов, Project Loom позволяет Java-программистам писать код в традиционном последовательном стиле потоков. Это дает дополнительные преимущества, например, позволяет отладчикам и профилировщикам работать в обычном режиме.
Создателям инструментов и инженерам среды исполнения придется проделать немного дополнительной работы для поддержки виртуальных потоков, но это лучше, чем навязывать дополнительную когнитивную нагрузку обычным Java-разработчикам.
Разработчики Loom полагают, что, поскольку виртуальные потоки никогда не нуждаются в объединении, они никогда не должны объединяться в пул. Вместо этого модель заключается в неограниченном создании виртуальных потоков. Для этого был добавлен неограниченный исполнитель. Доступ к нему можно получить с помощью нового фабричного метода Executors.newVirtualThreadExecutor().
По умолчанию планировщик для виртуальных потоков — это планировщик с распределением работы, введенный в ForkJoinPool. (Интересно, что аспект распределения работы в fork/join стал гораздо более важным, чем рекурсивная декомпозиция задач).
Дизайн Project Loom основан на понимании разработчиками вычислительных накладных расходов, которые будут присутствовать на различных потоках в их приложениях.
Просто говоря, если существует огромное количество потоков, которым постоянно требуется много процессорного времени, ваше приложение столкнется с нехваткой ресурсов, которую умное планирование не сможет решить. С другой стороны, если ожидается, что только несколько потоков станут привязанными к процессору, их следует поместить в отдельный пул и обеспечить платформенными потоками.
Виртуальные потоки также предназначены для эффективной работы в случае, когда множество потоков время от времени привязываются к процессору. Планировщик с распределением работы призван сглаживать загрузку процессора, а реальный код в конце концов вызовет операцию, которая пройдет точку передачи управления (например, блокирующий ввод-вывод).