Перейти к основному содержимому

Ткацкий станок проекта OpenJDK

· 5 мин. чтения

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 использует для реализации потоки ядра ОС , она не соответствует современным требованиям параллелизма. В частности, есть две основные проблемы:

  1. Потоки не могут соответствовать масштабу единицы параллелизма домена. Например, приложения обычно допускают до миллионов транзакций, пользователей или сеансов. Однако количество потоков, поддерживаемых ядром, намного меньше. Таким образом, поток T для каждого пользователя, транзакции или сеанса часто невозможен. ``
  2. Большинству параллельных приложений требуется некоторая синхронизация между потоками для каждого запроса. Из-за этого между потоками ОС происходит дорогостоящее переключение контекста.

Возможным решением таких проблем является использование асинхронных параллельных API . Распространенными примерами являются CompletableFuture и RxJava . При условии, что такие API не блокируют поток ядра, они предоставляют приложению более детализированную конструкцию параллелизма поверх потоков Java .

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

4. Задачи и планировщики

Любая реализация потока, легковесная или тяжеловесная, зависит от двух конструкций:

  1. Задача (также известная как продолжение) — последовательность инструкций, которая может приостанавливаться для некоторой блокирующей операции.
  2. Планировщик — для назначения продолжения ЦП и переназначения ЦП из приостановленного продолжения.

В настоящее время Java полагается на реализации ОС как для продолжения, так и для планировщика .

Теперь, чтобы приостановить продолжение, требуется сохранить весь стек вызовов. И аналогичным образом получить стек вызовов при возобновлении. Поскольку реализация продолжений в ОС включает собственный стек вызовов вместе со стеком вызовов Java, это приводит к значительному объему .

Однако более серьезной проблемой является использование планировщика ОС. Поскольку планировщик работает в режиме ядра, между потоками нет различий. И он обрабатывает каждый запрос процессора одинаково.

Этот тип планирования не является оптимальным, в частности, для Java-приложений .

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

Project Loom предлагает решить эту проблему с помощью потоков пользовательского режима, которые полагаются на реализацию продолжений и планировщиков во время выполнения Java, а не на реализацию ОС .

5. Волокна

В недавних прототипах OpenJDK новый класс с именем Fiber представлен в библиотеке наряду с классом Thread .

Поскольку планируемая библиотека для Fibers аналогична Thread , пользовательская реализация также должна оставаться аналогичной. Однако есть два основных отличия:

  1. Волокно завершит любую задачу внутренним продолжением пользовательского режима. Это позволило бы задаче приостанавливаться и возобновляться в среде выполнения Java, а не в ядре.
  2. Будет использоваться подключаемый планировщик пользовательского режима ( например, 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 с использованием потоков ядра.