Отказ от ответственности : В этом посте будут упоминаться некоторые термины, такие как параллелизм, потоки, многозадачность и другие, которые не будут подробно объяснены, но мы попытаемся добавить ссылки на другие посты или страницы о них.
Параллелизм в Java изначально управляется с помощью потоков ( java.lang. Thread ), который в основном является оболочкой/картографом для собственного потока операционной системы. Эта модель хорошо вписывается в систему, которой не требуется слишком много потоков, однако она имеет некоторые недостатки, когда мы хотим использовать их в больших масштабах, скажем, сотни или тысячи потоков.
Почему?
- Собственные потоки операционной системы должны поддерживать все языки программирования, они не оптимизированы для конкретного языка (возможно, C 🤔 ).
- Дорого переключение контекста .
- Высокое использование ресурсов памяти (размер стека).
Эти элементы создают проблемы для эскалации приложений java, использующих потоковый подход для выполнения параллельных заданий с использованием множества потоков.
Например. Приложение веб-сервера, обрабатывающее 500 запросов в секунду -> 500 потоков -> 500 Мб. Думая только о потоках, создаваемых для каждого запроса, наверняка веб-сервер использует другие потоки и память.
Вот где Project Loom является решением, поэтому прежде всего давайте определим, что такое Project Loom и что он привносит в мир Java.
Примечание Project Loom все еще является текущим проектом, и вся информация, названия и определения о нем могут измениться, и нет никакого официального JDK, выпущенного для работы.
Project Loom предназначен для изучения, инкубации и предоставления функций виртуальной машины Java и API, построенных поверх них, с целью поддержки простого в использовании, высокопроизводительного облегченного параллелизма и новых моделей программирования на платформе Java – Project Loom Wiki
Project Loom – это платформенное решение, представляющее концепцию Виртуального потока (он же Fiber), который представляет собой легкий поток, управляемый виртуальной машиной Java, а не операционной системой, и он соответствует существующим API-интерфейсам Java, допускающим синхронный (блокирующий) код.
Использование Project Loom в Java-приложении
Примечание В этом посте я хочу показать общий обзор и некоторые представления о том, как работают project loom и виртуальные потоки, а не показывать какой-либо микро-бенчмарк или что-то более глубокое.
Мы будем использовать jconsole для мониторинга производительности, а затем проверять, сколько потоков создано, сколько памяти используется и как ведет себя процессор.
Для этих тестов будет использоваться MacBook Pro 15″ 2018:
- 16 ГБ
- 2,2 ГГц 6-ядерный процессор Intel Core i7
- Mac OS Catalina
Поскольку нет никакого официального выпуска JDK, включая Project Loom, мы должны использовать двоичные файлы раннего доступа, предоставляемые проектом.
Мы можем загрузить их с официальной страницы проекта, эти двоичные файлы основаны на JDK 17, и есть варианты для двоичных файлов Linux, macOS и Windows. Сборки раннего доступа Project Loom
Как только двоичные файлы будут загружены и установлены в ПУТИ, мы сможем использовать проект JDK Loom:
❯ java --version openjdk 17-loom 2021-09-14 OpenJDK Runtime Environment (build 17-loom+2-42) OpenJDK 64-Bit Server VM (build 17-loom+2-42, mixed mode, sharing)
Нити
Давайте проведем наивный тест, создав приложение, которое создает 1000 потоков, и каждый поток выполняет несколько случайных математических операций между двумя числами в течение пары минут, а затем сравнивает производительность с использованием обычных собственных потоков и виртуальных потоков.
static class RandomNumbers implements Runnable { Random random = new Random(); @Override public void run() { for (int i = 0; i < 120; i++) { // during 120 seconds aprox try { int a = random.nextInt(); int b = random.nextInt(); int c = a * b; System.out.println("c = " + c); // print to avoid compiler remove the operation Thread.sleep(1000); // each operation every second } catch (InterruptedException e) { e.printStackTrace(); } } } }
Затем давайте создадим потоки.
public static void main(String[] args) throws InterruptedException { // Create 1000 native threads for (int i = 0; i < 1000; i++) { Thread t = new Thread(new RandomNumbers()); t.start(); } Thread.sleep(120000); }
Результаты собственных потоков
- Около 1000 потоков.
- Загрузка процессора составляет от 2 до 3 %.
- Постоянное использование памяти около 150 Мб.
Теперь давайте проделаем ту же операцию, используя виртуальные потоки
public static void main(String[] args) throws InterruptedException { // Create 1000 virtual threads for (int i = 0; i < 1000; i++) { Thread.startVirtualThread(new RandomNumbers()); } Thread.sleep(120000); }
Результаты виртуальных потоков
- Около 30 потоков
- Загрузка процессора менее 2%
- Увеличенное использование памяти с 20 МБ до 60 Мб.
Услуги исполнителя
Теперь давайте проведем тест, используя что-то более сложное, используя службу Executor для планирования потоков.
Служба исполнителя CachedThreadPool
public static void main(String[] args) throws InterruptedException { ExecutorService executor = Executors.newCachedThreadPool(); for (int i = 0; i < 1000; i++) { executor.submit(new RandomNumbers()); } executor.shutdown(); if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { executor.shutdownNow(); } }
Эта реализация executorservice использует новый поток (собственный) для каждой запланированной задачи, если только нет свободного потока для ее выполнения.
Эти результаты выглядят очень похоже на подход threads
- Около 1000 потоков.
- Загрузка процессора составляет около 2 %.
- Постоянное использование памяти около 130 Мб.
Исполнитель виртуального потока Служба исполнителя
public static void main(String[] args) throws InterruptedException { ExecutorService executor = Executors.newVirtualThreadExecutor(); for (int i = 0; i < 1000; i++) { executor.submit(new RandomNumbers()); } executor.shutdown(); if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { executor.shutdownNow(); } }
Эти результаты показывают небольшое снижение использования памяти и процессора.
- Около 30 потоков.
- Загрузка процессора составляет от 1 до 2 %.
- Увеличенное использование памяти от 30 МБ до 40 Мб.
Вывод
Мы видим, как использование виртуальных потоков может помочь нам, когда нам нужно использовать параллелизм с использованием тысяч потоков без ущерба для производительности и оптимального использования ресурсов.
Project Look обещает хорошую функцию для Java и ее API параллелизма, чтобы быть на уровне других языков, которые уже имеют облегченные модели параллелизма.
_ Если вам понравился этот пост, вы можете найти больше в https://jeisson.dev/blog/ и подписывайтесь на меня в твиттере @jeissonflorez29 👋
OpenJDK: Ткацкий станок
Оригинал: “https://dev.to/jeissonflorez29/project-loom-virtual-threads-in-java-418b”