Project loom: почему виртуальные потоки не используются по умолчанию?

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

Если это правда, то почему это разные вещи? Почему бы просто не сделать их стандартными? Есть ли причина не использовать их?

4 ответа

Здесь действительно есть два вопроса: 1. Почему виртуальные потоки не используются по умолчанию? и 2. Есть ли причина не использовать их.

Что касается значения по умолчанию, в Java действительно нет концепции потока «по умолчанию». После прибытия виртуальных потоков при создании потока вы должны указать, хотите ли вы поток платформы или виртуальный поток. Тогда возникает вопрос, почему мы решили не заменять автоматически сегодняшние потоки виртуальными потоками (т.е. создать виртуальный поток). Ответ на это довольно прост: это не принесет никакой пользы, а может быть и весьма вредным. Это было бы бесполезно, потому что преимущества виртуальных потоков заключаются в возможности создавать их очень много. Если ваше приложение сегодня создаст N потоков, превращение этих N потоков в виртуальные ничего не даст. Преимущество масштабирования виртуальных потоков проявляется только тогда, когда ваше приложение создает, скажем, 1000N потоков, а это означает, что его в любом случае нужно будет изменить (например, заменив с ). Это может быть вредно, потому что, хотя семантика виртуальных потоков почти такая же, как у платформенных потоков, они не обладают полной обратной совместимостью (подробности см. в JEP 425 ).

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

Имейте в виду, что Project Loom находится в активной экспериментальной разработке. Все может измениться.


Нет по умолчанию

Ты спросил:

Почему бы просто не сделать их стандартными?

В современной Java мы обычно не обращаемся к потокам напрямую. Вместо этого мы используем инфраструктуру Executors, добавленную много лет назад в Java 5.

В частности, в большинстве случаев Java-программист используетслужебный класс для производстваExecutorService. Эта служба-исполнитель поддерживается различными типами фабрик потоков или пулов потоков.

Например, если вы хотите сериализовать одну задачу за другой, мы будем использовать службу-исполнитель, поддерживаемую одним потоком .

      ExecutorService executorService = Executors.newSingleThreadExecutor() ;

Если вы просматриваете Executorsclass Javadoc, вы увидите множество вариантов. Ни один из них не является «по умолчанию». Программист выбирает один из них в соответствии с потребностями своей конкретной ситуации.

С Project Loom у нас будет как минимум еще один такой вариант на выбор. В предварительной сборке Java вызовите новыйExecutors.newVirtualThreadPerTaskExecutor()чтобы получить службу-исполнитель, поддерживаемую виртуальными потоками . Сходите с ума и бросьте на него миллион задач.

      ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor() ;

Ты спросил:

почему это разные вещи?

Одним из главных приоритетов для команды Java является обратная совместимость: существующие приложения должны запускаться без каких-либо неожиданностей.

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

Когда выбирать или избегать виртуальных потоков

Ты спросил:

Есть ли причина не использовать их?

Да, конечно. Две причины:

  • Задачи, связанные с процессором
  • Задачи, используемые для косвенного регулирования других ресурсов

Задачи, связанные с процессором

Весь смысл виртуальных потоков состоит в том, чтобы держать «настоящий» поток, поток платформы host-OS, занятым. Когда виртуальный поток блокируется, например, в ожидании ввода-вывода хранилища или ожидания сетевого ввода-вывода, виртуальный поток «демонтируется» из потока хоста, в то время как другой виртуальный поток «монтируется» в потоке хоста, чтобы выполнить некоторое выполнение.

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

Дросселирование

Еще одна причина, по которой следует избегать виртуальных потоков, заключается в том, что существующий код зависит от ограничений или узких мест потоков платформы, чтобы ограничить использование их приложением других ресурсов. Например, если сервер базы данных ограничен 10 одновременными подключениями, то некоторые приложения были написаны для использования службы-исполнителя, поддерживаемой только 8 или 9 потоками. Такой существующий код не следует слепо переключать на виртуальные потоки.

Конечно, такой код не оптимален. Такая кодовая база была бы лучше, яснее и очевиднее для понимания, если бы использовались явные механизмы ограничения/регулирования.

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

Java уже давно предлагает такие механизмы дросселирования. Они просто не всегда использовались, учитывая простоту/легкость полагаться на ограничения/узкие места ограниченного числа потоков платформы.


Я не эксперт в этом. Так что полагайтесь на тех, кто является экспертами. Для получения подробной информации и идей обязательно прочитайте статьи и посмотрите презентации и интервью Рона Пресслера, Алана Бейтмана или других членов команды Project Loom.

Давайте начнем с

Почему бы просто не сделать их стандартными?

Виртуальные потоки обернуты в потоки платформы, поэтому вы можете считать их иллюзией, которую обеспечивает JVM, вся идея состоит в том, чтобы сделать жизненный цикл потоков операциями, связанными с процессором .

  • Платформенные потоки против виртуальных потоков. Потоки платформы берут в заложники потоки ОС в задачах и операциях на основе ввода-вывода, ограниченных числом применимых потоков с пулом потоков и потоками ОС, по умолчанию они не являются потоками демона.

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

Архитектура четвертого уровня для лучшего понимания.

Процессор

  • Многоядерные многоядерные процессоры с выполнением операций в процессоре.

Операционные системы

  • Потоки ОС планировщик ОС выделяет время ЦП задействованным потокам ОС.

JVM

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

Виртуальные потоки со службой Executer

  • Более эффективно использовать службу исполнителя, поскольку она связана с пулом потоков и ограничена применимыми потоками с ним, однако по сравнению с виртуальными потоками, службой исполнителя и виртуальным содержимым нам не нужно обрабатывать или управлять связанным пулом потоков.

             try(ExecutorService service = Executors.newVirtualThreadPerTaskExecutor()) {
         service.submit(ExecutorServiceVirtualThread::taskOne);
         service.submit(ExecutorServiceVirtualThread::taskTwo);
     }
    
  • Служба исполнителя реализует интерфейс Auto Closable в JDK 19, таким образом, при использовании с «попыткой с ресурсом», как только он достигнет конца «попытки», заблокируйте вызываемый «закрыть» API, в качестве альтернативы основной поток будет ждать, пока все отправленные задачи с их выделенные виртуальные потоки завершают свой жизненный цикл, и соответствующий пул потоков отключается.

             ThreadFactory factory = Thread.ofVirtual().name("user thread-", 0).factory();
     try(ExecutorService service = Executors.newThreadPerTaskExecutor(factory)) {
         service.submit(ExecutorServiceThreadFactory::taskOne);
         service.submit(ExecutorServiceThreadFactory::taskTwo);
     }
    
  • Служба исполнителя также может быть создана с виртуальной фабрикой потоков, просто поставив фабрику потоков с аргументом конструктора.

  • Может использовать функции службы Executer, такие как Future и Completable Future.

Преимущества виртуальных потоков

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

Предостережения относительно использования виртуальных потоков

  • Не используйте монитор, т.е. синхронизированный блок, однако это будет исправлено в новой версии JDK, альтернативой этому является использование «ReentrantLock» с оператором try-final.

  • Блокировка с помощью собственных фреймов в стеке, JNI. это очень редко

  • Контроль памяти на стек (уменьшение локалей потока и отсутствие глубокой рекурсии)

  • Еще не обновленные инструменты мониторинга, такие как отладчики, JConsole, VisualVM и т. д.

Узнайте больше о JEP-425

Если вы установите их по умолчанию, значительная часть существующего кода Java не сможет просто переключиться на Java 19, потому что этот код оптимизирован для потоков ОС.Java должна быть обратно совместима.

Например, бывают случаи, когда виртуальные потоки не имеют особого смысла.

  • Приложения, выполняющие тяжелые вычисления
  • Если вы делаете запросы к БД с максимальным пулом соединений, узким местом являются не потоки.
  • Использование локальных переменных потока не является хорошей идеей с виртуальными потоками.

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

Другие вопросы по тегам