1. Обзор
В этой статье мы кратко рассмотрим Project Loom . По сути, основной целью Project Loom является поддержка высокопроизводительной и облегченной модели параллелизма в Java.
2. Проект Ткацкий станок
Project Loom — это попытка сообщества OpenJDK представить облегченную конструкцию параллелизма в Java. Прототипы Loom до сих пор вносили изменения в JVM, а также в библиотеку Java.
Хотя для Loom еще нет запланированного выпуска, мы можем получить доступ к последним прототипам на вики Project Loom .
Прежде чем мы обсудим различные концепции Loom, давайте обсудим текущую модель параллелизма в Java.
3. Модель параллелизма в Java
В настоящее время Thread
представляет собой основную абстракцию параллелизма в Java. Эта абстракция, наряду с другими параллельными API , упрощает написание параллельных приложений.
Однако, поскольку Java использует для реализации потоки ядра ОС , она не соответствует современным требованиям параллелизма. В частности, есть две основные проблемы:
Потоки
не могут соответствовать масштабу единицы параллелизма домена. Например, приложения обычно допускают до миллионов транзакций, пользователей или сеансов. Однако количество потоков, поддерживаемых ядром, намного меньше. Таким образом,поток
T
для каждого пользователя, транзакции или сеанса часто невозможен. ``- Большинству параллельных приложений требуется некоторая синхронизация между потоками для каждого запроса. Из-за этого между потоками ОС происходит дорогостоящее переключение контекста.
Возможным решением таких проблем является использование асинхронных параллельных API . Распространенными примерами являются CompletableFuture
и RxJava . При условии, что такие API не блокируют поток ядра, они предоставляют приложению более детализированную конструкцию параллелизма поверх потоков Java .
С другой стороны, такие API сложнее отлаживать и интегрировать с устаревшими API . Таким образом, требуется облегченная конструкция параллелизма, независимая от потоков ядра.
4. Задачи и планировщики
Любая реализация потока, легковесная или тяжеловесная, зависит от двух конструкций:
- Задача (также известная как продолжение) — последовательность инструкций, которая может приостанавливаться для некоторой блокирующей операции.
- Планировщик — для назначения продолжения ЦП и переназначения ЦП из приостановленного продолжения.
В настоящее время Java полагается на реализации ОС как для продолжения, так и для планировщика .
Теперь, чтобы приостановить продолжение, требуется сохранить весь стек вызовов. И аналогичным образом получить стек вызовов при возобновлении. Поскольку реализация продолжений в ОС включает собственный стек вызовов вместе со стеком вызовов Java, это приводит к значительному объему .
Однако более серьезной проблемой является использование планировщика ОС. Поскольку планировщик работает в режиме ядра, между потоками нет различий. И он обрабатывает каждый запрос процессора одинаково.
Этот тип планирования не является оптимальным, в частности, для Java-приложений .
Например, рассмотрим поток приложения, который выполняет некоторые действия с запросами, а затем передает данные другому потоку для дальнейшей обработки. Здесь было бы лучше запланировать оба этих потока на одном и том же процессоре . Но поскольку планировщик не зависит от потока, запрашивающего ЦП, это невозможно гарантировать.
Project Loom предлагает решить эту проблему с помощью потоков пользовательского режима, которые полагаются на реализацию продолжений и планировщиков во время выполнения Java, а не на реализацию ОС .
5. Волокна
В недавних прототипах OpenJDK новый класс с именем Fiber
представлен в библиотеке наряду с классом Thread .
Поскольку планируемая библиотека для Fibers
аналогична Thread
, пользовательская реализация также должна оставаться аналогичной. Однако есть два основных отличия:
Волокно
завершит любую задачу внутренним продолжением пользовательского режима. Это позволило бы задаче приостанавливаться и возобновляться в среде выполнения Java, а не в ядре.- Будет использоваться подключаемый планировщик пользовательского режима (
например, ForkJoinPool
) .
Давайте подробно рассмотрим эти два пункта.
6. Продолжения
Продолжение (или со-программа) — это последовательность инструкций, которая может быть завершена вызывающей стороной на более позднем этапе.
Каждое продолжение имеет точку входа и точку выхода. Точка текучести находится там, где она была приостановлена. Всякий раз, когда вызывающая сторона возобновляет продолжение, управление возвращается к последней точке выхода.
Важно понимать , что эта приостановка/возобновление теперь происходит в среде выполнения языка, а не в ОС . Следовательно, это предотвращает дорогостоящее переключение контекста между потоками ядра.
Как и в случае с потоками, Project Loom поддерживает вложенные волокна. Поскольку файберы внутренне зависят от продолжений, они также должны поддерживать вложенные продолжения. Чтобы лучше это понять, рассмотрим класс Continuation
, допускающий вложенность:
Continuation cont1 = new Continuation(() -> {
Continuation cont2 = new Continuation(() -> {
//do something
suspend(SCOPE_CONT_2);
suspend(SCOPE_CONT_1);
});
});
Как показано выше, вложенное продолжение может приостановить себя или любое из включенных в него продолжений, передав переменную области видимости .
По этой причине они известны как продолжения с ограниченной областью действия .
Поскольку приостановка продолжения также потребует сохранения стека вызовов, целью проекта Loom также является добавление облегченного извлечения стека при возобновлении продолжения.
7. Планировщик
Ранее мы обсуждали недостатки планировщика ОС в планировании взаимосвязанных потоков на одном ЦП.
Хотя цель Project Loom — разрешить подключаемые планировщики с волокнами, ForkJoinPool
в асинхронном режиме будет использоваться в качестве планировщика по умолчанию.
ForkJoinPool
работает по алгоритму work-stealing . Таким образом, каждый поток поддерживает очередь задач и выполняет задачу из своей головы. Кроме того, любой бездействующий поток не блокируется, ожидая задачи, а вместо этого извлекает ее из хвоста двухсторонней очереди другого потока.
Единственная разница в асинхронном режиме заключается в том, что рабочие потоки крадут задачу из головы другой двухсторонней очереди .
ForkJoinPool
добавляет задачу, запланированную другой запущенной задачей, в локальную очередь. Следовательно, выполнение его на том же процессоре.
8. Заключение
В этой статье мы обсудили проблемы в текущей модели параллелизма Java и изменения, предложенные Project Loom .
При этом мы также определили задачи и планировщики и рассмотрели, как Fibers и ForkJoinPool могут предоставить альтернативу Java с использованием потоков ядра.