Рубрики
Без рубрики

Проектный станок и виртуальные потоки в Java

Отказ от ответственности: В этом посте будут упомянуты некоторые термины, такие как параллелизм, потоки, многозадачность и другие… С тегами java, concurrency, loom, threads.

Отказ от ответственности : В этом посте будут упоминаться некоторые термины, такие как параллелизм, потоки, многозадачность и другие, которые не будут подробно объяснены, но мы попытаемся добавить ссылки на другие посты или страницы о них.

Параллелизм в 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”