Я люблю стректрейсы и понятный линейный код. И соответственно не люблю реактивщину. Все примеры будут нереактивными с последовательным понятным кодом.

Примеры запускались на доступной сегодня jdk.

openjdk version "19-loom" 2022-09-20 
OpenJDK Runtime Environment (build 19-loom+6-625) 
OpenJDK 64-Bit Server VM (build 19-loom+6-625, mixed mode, sharing)

Не забываем про --enable-preview флажок.

В этой jdk доступны такие методы для экспериментирования с виртуальными потоками:

/**
 * Creates a virtual thread to execute a task and schedules it to execute.
 *
 * <p> This method is equivalent to:
 * <pre>{@code Thread.ofVirtual().start(task); }</pre>
 *
 * @param task the object to run when the thread executes
 * @return a new, and started, virtual thread
 * @throws UnsupportedOperationException if preview features are not enabled
 * @see <a href="#inheritance">Inheritance when creating threads</a>
 * @since 19
 */
@PreviewFeature(feature = PreviewFeature.Feature.VIRTUAL_THREADS)
public static Thread startVirtualThread(Runnable task) { ... }

и

/**
 * Creates an Executor that starts a new virtual Thread for each task.
 * The number of threads created by the Executor is unbounded.
 *
 * <p> This method is equivalent to invoking
 * {@link #newThreadPerTaskExecutor(ThreadFactory)} with a thread factory
 * that creates virtual threads.
 *
 * @return a new executor that creates a new virtual Thread for each task
 * @throws UnsupportedOperationException if preview features are not enabled
 * @since 19
 */
@PreviewFeature(feature = PreviewFeature.Feature.VIRTUAL_THREADS)
public static ExecutorService newVirtualThreadPerTaskExecutor() { .... }

Не очень много, но для экспериментов хватит.

Общий код запуска тестов:

@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 1)
@Measurement(iterations = 2)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Benchmark)
public class BenchmarkThreading {

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(BenchmarkThreading.class.getSimpleName())
                .forks(1)
                .build();
        new Runner(opt).run();
    }

    //тут тесты
}

Производительность

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

@Benchmark
public void testCreateVirtualThread(Blackhole blackhole) {
    for (int i=0; i<100; ++i) {
        int finalI = i;
        Thread.startVirtualThread(() -> blackhole.consume(finalI));
    }
}

@Benchmark
public void testCreateThread(Blackhole blackhole) {
    for (int i = 0; i < 1000; ++i) {
        int finalI = i;
        var thread = new Thread(() -> blackhole.consume(finalI));
        thread.start();
    }
}
Benchmark                                   Mode  Cnt       Score   Error  Units
BenchmarkThreading.testCreateThread         avgt       199158,959          us/op
BenchmarkThreading.testCreateVirtualThread  avgt           53,674          us/op

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

А что они нам дадут в более-менее реальных примерах использования? Нормальная программа на Джаве не создает потоки в нагруженных участках кода, а использует пулы и экзекуторы.

Попробуем экзекутором выполнить микрозадачи:

@Benchmark
public void testVirtualExecutorSmallTask(Blackhole blackhole) {
    try(var executor = Executors.newVirtualThreadPerTaskExecutor()){
        for (int i = 0; i < 100; ++i) {
            int finalI = i;
            executor.submit(() -> blackhole.consume(finalI));
        }
    }
}

@Benchmark
public void testCachedExecutorSmallTask(Blackhole blackhole) throws InterruptedException {
    try(var executor = Executors.newCachedThreadPool()){
        for (int i = 0; i < 100; ++i) {
            int finalI = i;
            executor.submit(() -> blackhole.consume(finalI));
        }
    }
}

@Benchmark
public void testFixedExecutorSmallTask(Blackhole blackhole) throws InterruptedException {
    try(var executor = Executors.newFixedThreadPool(20)){
        for (int i = 0; i < 100; ++i) {
            int finalI = i;
            executor.submit(() -> blackhole.consume(finalI));
        }
    }
}
Benchmark                                        Mode  Cnt     Score   Error  Units
BenchmarkThreading.testCachedExecutorSmallTask   avgt    2  1233,639          us/op
BenchmarkThreading.testFixedExecutorSmallTask    avgt    2  2156,590          us/op
BenchmarkThreading.testVirtualExecutorSmallTask  avgt    2    96,231          us/op

Результат тоже хорош. За исключение того что с размером fixed пула я не угадал. Ну ладно, на практике в продакшен коде типовой мидл тоже никогда не угадает.

А что если сделать тест еще ближе к реальности? В нормальном коде в поток выносят операции занимающее какое-то значимое количество времени.

На моей тестовой машине Blackhole.consumeCPU(100_000_000) занимает около 200мс что можно принять разумным временем на задачу которую уже можно отправлять в отдельный поток.

@Benchmark
public void testVirtualExecutorNormalTask(Blackhole blackhole) {
    try(var executor = Executors.newVirtualThreadPerTaskExecutor()){
        for (int i = 0; i < 100; ++i) {
            executor.submit(() -> Blackhole.consumeCPU(100_000_000));
        }
    }
}

@Benchmark
public void testCachedExecutorNormalTask(Blackhole blackhole) throws InterruptedException {
    try(var executor = Executors.newCachedThreadPool()){
        for (int i = 0; i < 100; ++i) {
            executor.submit(() -> Blackhole.consumeCPU(100_000_000));
        }
    }
}

@Benchmark
public void testFixedExecutorNormalTask(Blackhole blackhole) throws InterruptedException {
    try(var executor = Executors.newFixedThreadPool(20)){
        for (int i = 0; i < 100; ++i) {
            executor.submit(() -> Blackhole.consumeCPU(100_000_000));
        }
    }
}
Benchmark                                         Mode  Cnt        Score   Error  Units
BenchmarkThreading.testCachedExecutorNormalTask   avgt    2  5249759,575          us/op
BenchmarkThreading.testFixedExecutorNormalTask    avgt    2  5247051,750          us/op
BenchmarkThreading.testVirtualExecutorNormalTask  avgt    2  5246058,750          us/op

Разницы нет. Это было ожидаемо. На такой нагрузке работа с потоками занимает пренебрежимо малое время по сравнению с бизнес логикой. Не загромождая статью исходниками покажу результат для других значений Blackhole.consumeCPU(ххх)

10_000_000 или 20мс на задачу
Benchmark                                         Mode  Cnt       Score   Error  Units
BenchmarkThreading.testCachedExecutorNormalTask   avgt    2  553018,934          us/op
BenchmarkThreading.testFixedExecutorNormalTask    avgt    2  564500,005          us/op
BenchmarkThreading.testVirtualExecutorNormalTask  avgt    2  530236,755          us/op

1_000_000 или 2мс на задачу
Benchmark                                         Mode  Cnt      Score   Error  Units
BenchmarkThreading.testCachedExecutorNormalTask   avgt    2  65124,411          us/op
BenchmarkThreading.testFixedExecutorNormalTask    avgt    2  54710,276          us/op
BenchmarkThreading.testVirtualExecutorNormalTask  avgt    2  53285,513          us/op

100_000 или 0.2мс на задачу
Benchmark                                         Mode  Cnt      Score   Error  Units
BenchmarkThreading.testCachedExecutorNormalTask   avgt    2  14088,289          us/op
BenchmarkThreading.testFixedExecutorNormalTask    avgt    2   8267,134          us/op
BenchmarkThreading.testVirtualExecutorNormalTask  avgt    2   5792,022          us/op

10_000 или 0.02мс на задачу
Benchmark                                         Mode  Cnt     Score   Error  Units
BenchmarkThreading.testCachedExecutorNormalTask   avgt    2  2377,223          us/op
BenchmarkThreading.testFixedExecutorNormalTask    avgt    2  2757,024          us/op
BenchmarkThreading.testVirtualExecutorNormalTask  avgt    2   664,795          us/op

Разница становится явно видна на совсем маленьких задачах. Там где менеджмент потоков начинает занимать значимое время от всей остальной логики.

Можно сделать вывод что в типовом нормальном Джава коде плюсов по производительности от простого включения виртуальных потоков мы не заметим. Если вы у себя её заметили, то стоит покопаться по коду поискать где вы используете потоки для слишком маленьких задач.

Зато мы получаем возможность кидать в отдельный поток просто все что угодно. Разница для микрозадач колоссальна. Это откроет некоторые возможности более удобно писать код и лучше утилизировать все доступные ядра во вроде бы однопоточном коде. Может быть наконец-то появится смысл в .parallelStream() при использовании виртуальных потоков внутри.

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

АПД: Следующий тест производительности добавлен по просьбе из комментариев. Спасибо комментатору, который напомнил мне что никогда нельзя доверять мысленным тестам производительности и всегда надо всё проверять на бенчмарками.

Я для этого теста буду использовать немного другие параметры экзекутора. Для максимального приближения к реальности. Допустим есть http сервер с пулом принимающим соединения в 100 потоков и выполняющий в них задачи с паузами в 100мс. У меня на машине всего 4 ядра, значит при нагрузке более 4% скорость работы теста определяется быстродейсвием кода, а не чем-то еще.

Экзекуторы:

Executors.newVirtualThreadPerTaskExecutor()
Executors.newCachedThreadPool()
Executors.newFixedThreadPool(100))

Тест:

@Benchmark
public void testCachedExecutorNormalTask() {
    try(var executor = Executors.newCachedThreadPool()){
        for (int i = 0; i < 100; ++i) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            executor.submit(() -> Blackhole.consumeCPU(10_000_000));
        }
    }
}

Результаты:

100мс ожидания + 20мс работы. Нагрузка пула около 15%, 
что очень блико к реальной продакшен нагрузке хорошо сконфигуренного сервера.
Benchmark                                         Mode  Cnt       Score   Error  Units
BenchmarkThreading.testCachedExecutorNormalTask   avgt    2  677262,310          us/op
BenchmarkThreading.testFixedExecutorNormalTask    avgt    2  659306,103          us/op
BenchmarkThreading.testVirtualExecutorNormalTask  avgt    2  679307,987          us/op	

100мс ожидания + 2мс работы. Тут скорость уже перестала зависеть от скорости работы кода.
Benchmark                                         Mode  Cnt       Score   Error  Units
BenchmarkThreading.testCachedExecutorNormalTask   avgt    2  196600,036          us/op
BenchmarkThreading.testFixedExecutorNormalTask    avgt    2  191974,914          us/op
BenchmarkThreading.testVirtualExecutorNormalTask  avgt    2  196900,749          us/op

Результаты показывают что при типичной нагрузке на типичный пул http сервера виртуальные потоки не дают никакого заметного ускорения. Если цифры как следует выкрутить в сторону уменьшения аналогично предыдущим тестам мы получим ускорение. Но положа руку на сердце у кого из вас АПИшка работает на порядки быстрее чем в этом тесте?

А что с памятью?

Уже давно ходят слухи что потоки в Джаве очень прожорливы до памяти. Я читал версии что каждый поток стоит мегабайты памяти просто так на создание. И виртуальные потоки всех нас спасут от покупки дополнительной памяти в наши кластера.

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

Приложение для оценки простейшее:

public static void main(String[] args) {
    for(int i=0; i<100; ++i) {
        var thread = new Thread(() -> {
            Blackhole.consumeCPU(1);
            try {
                Thread.sleep(100_000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        thread.start();
    }
    System.exit(0);
}

JDK17 LTS. Тех кто еще не обновился мне уже даже не жалко. Давно пора обновиться было.

openjdk version "17.0.3" 2022-04-19 
OpenJDK Runtime Environment Temurin-17.0.3+7 (build 17.0.3+7) 
OpenJDK 64-Bit Server VM Temurin-17.0.3+7 (build 17.0.3+7, mixed mode, sharing)

Никаких особых ключей запуска: -XX:+UnlockDiagnosticVMOptions -XX:NativeMemoryTracking=summary -XX:+PrintNMTStatistics -Xmx4G

71 
Thread (reserved=75855304, committed=4_793_800) 
  (thread #71) (stack: reserved=75497472, committed=4435968) 
  (malloc=178456 #744) 
  (arena=179376 #245)

1_016 
Thread (reserved=1069151112, committed=65_549_192) 
  (thread #1016) (stack: reserved=1066401792, committed=62799872) 
  (malloc=1526056 #7146) 
  (arena=1223264 #2040)

10_018 
Thread (reserved=10532726584, committed=643_856_184) 
  (thread #10018) (stack: reserved=10505682944, committed=616812544) 
  (malloc=15013392 #70316) 
  (arena=12030248 #20051)

Видна хорошая закономерность с расходом около 64 килобайт памяти на пустой поток.

Виртуальные потоки в этом месте память под себя не требуют, и будут занимать что-то схожее с типичным Джава объектом размером в десятки-сотни байт. Можно упрощенно считать что это 0 по сравнению с 64 килобайтами на классический поток.

Выводы

Отрицательно:

  • Нас ждут увлекательные баталии в код ревью о новых практиках написания кода.

  • Количество ошибок с многопоточностью заметно возрастет.

Нейтрально:

  • Виртуальные потоки не дадут никакого ускорения в типичном джава приложении без переписывания кода.

  • Виртуальные потоки не уменьшат потребление памяти нормально сделанным приложением. 64 килобайта * 1_000 типовых потоков, это неинтересно.

Положительно:

  • Виртуальные потоки дадут возможность по новому писать код. Паралелим все что не запрещено математикой.

  • .parallelStream обретает смысл.

  • Виртуальные потоки дадут возможность более эффективно утилизировать доступные ядра. Без выделения больших независимых кусков кода и реактивщины.

Комментарии (26)


  1. harios
    11.07.2022 10:56

    Виртуальные потоки не дадут никакого ускорения в типичном джава приложении без переписывания кода

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


    1. sandersru
      11.07.2022 11:10
      +6

      Скорее простой программист их даже не заметит. Они будут переписаны где то под капотом того же Springboot/Netty/Quarkus/etc


    1. Flowka
      11.07.2022 12:36
      +4

      не забывайте, что это превью фича и очень многое еще может поменяться. Кроме того, в JEP 425 нет задачи, чтобы код переписанный на virtual threads работал быстрее. Речь идет о замене парадигмы разработки на thread-per-request при минимальном переписывании кода приложения. Что в теории должно сильно упростить разработку и дебаг.


  1. ris58h
    11.07.2022 15:37
    +5

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

    Утверждение основывается на бенчмарке с Blackhole.consumeCPU?

    Открываем блог-пост на сайте Oracle Coming to Java 19: Virtual threads and platform threads:

    It’s important to understand what virtual threads are for—and what they are not for.

    Never forget that virtual threads aren’t faster threads. Virtual threads don’t magically execute more instructions per second than platform threads do.

    What virtual threads are really good for is waiting.

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

    Короче, утверждение про "никакого ускорения в типичном джава приложении" спорно. Хотя бы потому что не очень понятно что такое "типичное джава приложение". Числодробилка? Прослойка между фронтом и БД?


    1. BugM Автор
      11.07.2022 16:21

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

      Я считаю типовым приложением любую АПИшку с разной степенью навороченности бизнес логики в ней. Или любой фоновый обработчик который проводит большую часть времени на IO.

      Цитата из блога Оракла говорит ровно тоже самое. Не ждите что ваш код просто станет быстрее работать. Я в статье нашёл на каких порядках цифр появляется разница. Для потоков висящих на IO результат будет таким же. Тут время висения в одном потоке важно, а не то на что оно ушло.


      1. ris58h
        11.07.2022 17:37
        +1

        Цитата из блога Оракла говорит ровно тоже самое. Не ждите что ваш код просто станет быстрее работать.

        Она говорит, что сам тред не будет работать быстрее, но если работы нет, то появляется возможность для улучшения.

        Для потоков висящих на IO результат будет таким же.

        Именно такой тест я бы и хотел увидеть в статье. Пока ваше утверждение не подкреплено фактами.

        Тут время висения в одном потоке важно, а не то на что оно ушло.

        Очень важно на что оно ушло. Если тред занят работой, то мы ничего с ним сделать не можем, а вот если тред просто ждёт, то его можно переиспользовать для другой работы. В этом и есть суть Loom.


        1. sandersru
          11.07.2022 22:51

          https://github.com/quarkusio/quarkus/pull/24942 вот тут был другой эксперимент по переводу текущего фрэймворка.

          Ребята результаты не анонсируют, но из личных разговоров в некоторых местах стало лучше на несколько нулей.

          Цифры приводить не буду, лучше дождаться официального релиза


        1. BugM Автор
          12.07.2022 01:56

          Именно такой тест я бы и хотел увидеть в статье. Пока ваше утверждение не подкреплено фактами.

          Я не понимаю ни как провести этот тест ни каких других результатов вы от него ждете.

          Схема теста: Есть у нас пул из 100 потоков в которых IO bound задачи. Для теста сделаем так что они 100% времени висят на IO. Когда они доработают? Как IO отпустит, так сразу и отработают. Насколько велика будет разница в расходе ресурсов в виртуальных и в реальных потоках? Зависит от скорости отработки IO. Она будет ровно такая же как в мое CPU bound тесте. Почему бы ей быть другой?

          Очень важно на что оно ушло. Если тред занят работой, то мы ничего с ним сделать не можем, а вот если тред просто ждёт, то его можно переиспользовать для другой работы. В этом и есть суть Loom.

          Конечно. Но как это определение превратить в реальные цифры пользы совершенно непонятно. Сколько там будет той пользы тоже непонятно. Это я и попробовал сделать.


          1. svr_91
            12.07.2022 08:15
            +1

            Зависит от скорости отработки IO. Она будет ровно такая же как в мое CPU bound тесте. Почему бы ей быть другой?

            Если я правильно понимаю, то механизм работы IO абсолютно разный. В случае с настоящими тредами IO системное, тоесть замораживается и размораживается системный поток, что ведет например к некоему промежутку между реальным наступлением события и реальной разморозкой, в случае же virtual thread там может быть busy loop (по крайней мере, когда есть какие-либо еще задачи)

            В любом случае, странно писать статью с замерами, чтобы потом лишь делиться своими догадками


          1. ris58h
            12.07.2022 15:51
            +1

            Ситуация: у вас 2 треда в пуле (просто ради примера). Приходит первый пользователь и ваша "тяжелая апишка с неплохим входящим РПС" занимает тред тем, что он ждёт ответа от БД. Приходит второй пользователь и занимает второй тред. Теперь приходит третий пользователь и отваливается, т.к. некому его обслужить - все треды заняты. Ну как заняты - не делают ничего и тупо висят на IO.

            Теперь подключаем Loom с виртуальными тредами и, магическим образом, третьего пользователя есть кому обслужить - этим займётся один из двух тредов, который ждёт IO. И ни какого "Придется код писать". Вся магия внутри фреймворка/библиотеки, а код остаётся тем же последовательным и блокирующим, но только с виду. Look Ma, no callbacks!

            Вот такой тест и стоит сделать, а не "так что они 100% времени висят на IO". Типичное джава приложение.


            1. BugM Автор
              12.07.2022 16:09

              Делаем пул в 100 потоков. Если мало делаем 200. Это правда много и хватит на большой РПС. Это уже так сделано и работает в типичных http серверах.

              Loom поможет этим потокам стать виртуальными и их можно будет сделать тысячу.

              Без переписывания кода эта трансформация не даст ничего. Если взять медиану работы быстрого апи за 5мс, то по моим измерения там все в пределах погрешности. С переписыванием (веб сервера для использования бонусов виртуальных потоков) может стать лучше. Надо код писать.


              1. svr_91
                12.07.2022 16:28
                +2

                Пул в 100 потоков будет постоянно переключать контекст каждого потока туда-обратно, если у вас конечно не сервер с 100 ядров. А Loom на теже 100 виртуальных потоков (и 16 реальных например, интересно, сколько реальных тредов выделяет Loom для работы) устранит переключение контекста и только за счет этого можно будет выиграть сколько-то


                1. BugM Автор
                  12.07.2022 18:53

                  Мои измерения показали что при времени обработки больше миллисекунды там все в пределах погрешности получается. Без специфичных доработок кода. Средний обработчик запроса занимает заметно больше.

                  Стоимость переключения контекста на длинных задачах переоценена. Миллисекунды это много.


              1. ris58h
                12.07.2022 18:19

                С переписыванием (веб сервера для использования бонусов виртуальных потоков) может стать лучше. Надо код писать.

                Этим занимаются создатели фреймворков/библиотек. Ваш код с бизнеслогикой остаётся тем же. Код писать не надо.

                Мой посыл в том, что в своём сравнении вы проигнорировали самую суть Loom.


                1. BugM Автор
                  12.07.2022 18:56

                  Я про суть и пытался.

                  На каких характерных цифрах получается ускорение? В рекламе все идеально. Миллион виртуальных потоков и все летает.

                  А вот в реальности в пуле из пары сотен потоков и временем в каждом потоке миллисекунд 10 как-то ничего не заметно.

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


                  1. svr_91
                    13.07.2022 07:56

                    А вот в реальности в пуле из пары сотен потоков и временем в каждом потоке миллисекунд 10 как-то ничего не заметно.

                    Может быть к релизу что-то изменится

                    Я извиняюсь, а какая у вас цель? Чтобы в виртуальном потоке таска на 10 мс исполнялась быстрее, чем за 10 мс?


                    1. BugM Автор
                      13.07.2022 11:14

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


                      1. ris58h
                        13.07.2022 18:18

                        https://wiki.openjdk.org/display/loom/Getting+started

                        Virtual threads are
                        best suited to executing code that spends most of its time blocked,
                        waiting for data to arrive on a network socket or waiting for an element
                        in queue for example.

                        Все кейсы из вашей статьи не об этом, к сожалению.


                      1. BugM Автор
                        13.07.2022 21:40

                        Дописал. Спасибо за идею что еще надо проверить.

                        Ищите по словам "Следующий тест производительности добавлен по просьбе из комментариев "


                      1. ris58h
                        14.07.2022 03:28

                        Так sleep надо внутри виртуального треда вызывать, а не снаружи.


  1. akurilov
    11.07.2022 21:06
    +4

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

    И дело вовсе не в скорости создания, автор здесь либо лукавит, либо ошибочно исходит из неверных предположений. Долгое создание потоков давно научились обходить тредпулами. Да только вот оптимальное число наивных потоков равно кол-ву ядер процессора. А обрабатывать может понадобиться и 1000 одновременных запросов, а то и больше. Вот здесь то и начинаются трудности написания "прямого" кода. С виртуальными потоками такой проблемы нет.

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


    1. sandersru
      11.07.2022 22:42

      А как же vert.x в java? В принципе он эту проблему решает. И в том же кваркус живёт под капотом 2мя слоями ниже.

      Да на уровне jdk это конечно лучше, но всеж


      1. akurilov
        11.07.2022 23:01
        +1

        Вы не поверите, даже я свой костыль для этого делал https://github.com/akurilov/fiber4j

        Отлично зарекомендовал себя на исполнении одновременно миллиона задач с ручным сохранением контекста.


        1. sandersru
          11.07.2022 23:17

          Не, в IT костыли наше все, но...

          А зачем? Когда vert.x давно существует. А smalrye оборачивает его в нормальное API вместо матрёшки в матрешках.


          1. akurilov
            11.07.2022 23:58

            Может быть потому что это немного другое. Для моих целей Vert.x был неприменим


    1. BugM Автор
      12.07.2022 02:03

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

      Миллион да. Я сейчас посмотрел на свой прод и там максимум это около 2.000 потоков на приложеньку. Очень большую и нагруженную приложеньку.

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

      Да только вот оптимальное число наивных потоков равно кол-ву ядер процессора. А обрабатывать может понадобиться и 1000 одновременных запросов, а то и больше. Вот здесь то и начинаются трудности написания "прямого" кода. С виртуальными потоками такой проблемы нет.

      В теории да, на практике нет.

      Ждущий поток не стоит почти ничего. 64 килобайта памяти. Тот же jetty на нагруженной апишке отлично живет с пулом в 100 потоков. Это тяжелая апишка с неплохим входящим РПС.

      Ну и возможность отскалироваться накликав еще по ядру на каждый шард стоит дорого. Она почти всегда полезнее процента производительности от подгона потоков под ядра.

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

      Полностью согласен. Уже понятно как фичу сделать правильно значит можно ее и в Джаву затаскивать.

      Стоило ли это делать 5 лет назад? Не уверен.

      А 10 лет назад? Точно нет.