Я люблю стректрейсы и понятный линейный код. И соответственно не люблю реактивщину. Все примеры будут нереактивными с последовательным понятным кодом.
Примеры запускались на доступной сегодня 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)
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.
Откуда следует, что никакого прироста для вычислительных задач не заявляется и не ожидается, а прирост ожадается для задач с большим количеством блокирующего кода. Такой тест, как мне кажется, и стоило включить в статью.
Короче, утверждение про "никакого ускорения в типичном джава приложении" спорно. Хотя бы потому что не очень понятно что такое "типичное джава приложение". Числодробилка? Прослойка между фронтом и БД?
BugM Автор
11.07.2022 16:21Числодробилки это к плюсам скорее. На джаве они тоже есть, но они вылизаны и делаются по другому. Про них можно отдельно писать. Там много неочевидных вещей. Loom им может помочь в плане более удобной утилизации ядер. Но тут нужна версия постабильнее чтобы посчитать что там получается.
Я считаю типовым приложением любую АПИшку с разной степенью навороченности бизнес логики в ней. Или любой фоновый обработчик который проводит большую часть времени на IO.
Цитата из блога Оракла говорит ровно тоже самое. Не ждите что ваш код просто станет быстрее работать. Я в статье нашёл на каких порядках цифр появляется разница. Для потоков висящих на IO результат будет таким же. Тут время висения в одном потоке важно, а не то на что оно ушло.
ris58h
11.07.2022 17:37+1Цитата из блога Оракла говорит ровно тоже самое. Не ждите что ваш код просто станет быстрее работать.
Она говорит, что сам тред не будет работать быстрее, но если работы нет, то появляется возможность для улучшения.
Для потоков висящих на IO результат будет таким же.
Именно такой тест я бы и хотел увидеть в статье. Пока ваше утверждение не подкреплено фактами.
Тут время висения в одном потоке важно, а не то на что оно ушло.
Очень важно на что оно ушло. Если тред занят работой, то мы ничего с ним сделать не можем, а вот если тред просто ждёт, то его можно переиспользовать для другой работы. В этом и есть суть Loom.
sandersru
11.07.2022 22:51https://github.com/quarkusio/quarkus/pull/24942 вот тут был другой эксперимент по переводу текущего фрэймворка.
Ребята результаты не анонсируют, но из личных разговоров в некоторых местах стало лучше на несколько нулей.
Цифры приводить не буду, лучше дождаться официального релиза
BugM Автор
12.07.2022 01:56Именно такой тест я бы и хотел увидеть в статье. Пока ваше утверждение не подкреплено фактами.
Я не понимаю ни как провести этот тест ни каких других результатов вы от него ждете.
Схема теста: Есть у нас пул из 100 потоков в которых IO bound задачи. Для теста сделаем так что они 100% времени висят на IO. Когда они доработают? Как IO отпустит, так сразу и отработают. Насколько велика будет разница в расходе ресурсов в виртуальных и в реальных потоках? Зависит от скорости отработки IO. Она будет ровно такая же как в мое CPU bound тесте. Почему бы ей быть другой?
Очень важно на что оно ушло. Если тред занят работой, то мы ничего с ним сделать не можем, а вот если тред просто ждёт, то его можно переиспользовать для другой работы. В этом и есть суть Loom.
Конечно. Но как это определение превратить в реальные цифры пользы совершенно непонятно. Сколько там будет той пользы тоже непонятно. Это я и попробовал сделать.
svr_91
12.07.2022 08:15+1Зависит от скорости отработки IO. Она будет ровно такая же как в мое CPU bound тесте. Почему бы ей быть другой?
Если я правильно понимаю, то механизм работы IO абсолютно разный. В случае с настоящими тредами IO системное, тоесть замораживается и размораживается системный поток, что ведет например к некоему промежутку между реальным наступлением события и реальной разморозкой, в случае же virtual thread там может быть busy loop (по крайней мере, когда есть какие-либо еще задачи)
В любом случае, странно писать статью с замерами, чтобы потом лишь делиться своими догадками
ris58h
12.07.2022 15:51+1Ситуация: у вас 2 треда в пуле (просто ради примера). Приходит первый пользователь и ваша "тяжелая апишка с неплохим входящим РПС" занимает тред тем, что он ждёт ответа от БД. Приходит второй пользователь и занимает второй тред. Теперь приходит третий пользователь и отваливается, т.к. некому его обслужить - все треды заняты. Ну как заняты - не делают ничего и тупо висят на IO.
Теперь подключаем Loom с виртуальными тредами и, магическим образом, третьего пользователя есть кому обслужить - этим займётся один из двух тредов, который ждёт IO. И ни какого "Придется код писать". Вся магия внутри фреймворка/библиотеки, а код остаётся тем же последовательным и блокирующим, но только с виду. Look Ma, no callbacks!
Вот такой тест и стоит сделать, а не "так что они 100% времени висят на IO". Типичное джава приложение.
BugM Автор
12.07.2022 16:09Делаем пул в 100 потоков. Если мало делаем 200. Это правда много и хватит на большой РПС. Это уже так сделано и работает в типичных http серверах.
Loom поможет этим потокам стать виртуальными и их можно будет сделать тысячу.
Без переписывания кода эта трансформация не даст ничего. Если взять медиану работы быстрого апи за 5мс, то по моим измерения там все в пределах погрешности. С переписыванием (веб сервера для использования бонусов виртуальных потоков) может стать лучше. Надо код писать.
svr_91
12.07.2022 16:28+2Пул в 100 потоков будет постоянно переключать контекст каждого потока туда-обратно, если у вас конечно не сервер с 100 ядров. А Loom на теже 100 виртуальных потоков (и 16 реальных например, интересно, сколько реальных тредов выделяет Loom для работы) устранит переключение контекста и только за счет этого можно будет выиграть сколько-то
BugM Автор
12.07.2022 18:53Мои измерения показали что при времени обработки больше миллисекунды там все в пределах погрешности получается. Без специфичных доработок кода. Средний обработчик запроса занимает заметно больше.
Стоимость переключения контекста на длинных задачах переоценена. Миллисекунды это много.
ris58h
12.07.2022 18:19С переписыванием (веб сервера для использования бонусов виртуальных потоков) может стать лучше. Надо код писать.
Этим занимаются создатели фреймворков/библиотек. Ваш код с бизнеслогикой остаётся тем же. Код писать не надо.
Мой посыл в том, что в своём сравнении вы проигнорировали самую суть Loom.
BugM Автор
12.07.2022 18:56Я про суть и пытался.
На каких характерных цифрах получается ускорение? В рекламе все идеально. Миллион виртуальных потоков и все летает.
А вот в реальности в пуле из пары сотен потоков и временем в каждом потоке миллисекунд 10 как-то ничего не заметно.
Может быть к релизу что-то изменится, но я не уверен. Вероятнее что плюсы будут в доступности других вариантов написания кода. Из которых вероятно получится что-то выжать.
svr_91
13.07.2022 07:56А вот в реальности в пуле из пары сотен потоков и временем в каждом потоке миллисекунд 10 как-то ничего не заметно.
Может быть к релизу что-то изменится
Я извиняюсь, а какая у вас цель? Чтобы в виртуальном потоке таска на 10 мс исполнялась быстрее, чем за 10 мс?
BugM Автор
13.07.2022 11:14Найти кейсы где виртуальные потоки начинают приносить пользу. Исходя из типичных практик написания кода на джаве.
ris58h
13.07.2022 18:18https://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.Все кейсы из вашей статьи не об этом, к сожалению.
BugM Автор
13.07.2022 21:40Дописал. Спасибо за идею что еще надо проверить.
Ищите по словам "Следующий тест производительности добавлен по просьбе из комментариев "
akurilov
11.07.2022 21:06+4Если автор так любит "прямой" код и не любит реактивность, то должен был понять, что с виртуальными потоками теперь как раз можно писать такой код. Можно выделять виртуальный поток на запрос и не париться, что их будет миллион. Нативные потоки такое не потянут, сервис захлебнется где то на сотнях.
И дело вовсе не в скорости создания, автор здесь либо лукавит, либо ошибочно исходит из неверных предположений. Долгое создание потоков давно научились обходить тредпулами. Да только вот оптимальное число наивных потоков равно кол-ву ядер процессора. А обрабатывать может понадобиться и 1000 одновременных запросов, а то и больше. Вот здесь то и начинаются трудности написания "прямого" кода. С виртуальными потоками такой проблемы нет.
Виртуальные потоки, файберы, корутины и гопутины - прекрасно зарекомендовали себя в других языках. И в Java необходимость в них давно назрела и перезрела
sandersru
11.07.2022 22:42А как же vert.x в java? В принципе он эту проблему решает. И в том же кваркус живёт под капотом 2мя слоями ниже.
Да на уровне jdk это конечно лучше, но всеж
akurilov
11.07.2022 23:01+1Вы не поверите, даже я свой костыль для этого делал https://github.com/akurilov/fiber4j
Отлично зарекомендовал себя на исполнении одновременно миллиона задач с ручным сохранением контекста.
BugM Автор
12.07.2022 02:03Можно выделять виртуальный поток на запрос и не париться, что их будет миллион.
Миллион да. Я сейчас посмотрел на свой прод и там максимум это около 2.000 потоков на приложеньку. Очень большую и нагруженную приложеньку.
Как делать приложеньки лучше с возможностью использовать миллион почти бесплатных потоков я представляю и описал. Но текущих приложенек это не коснется. Придется код писать. Как обычно.
Да только вот оптимальное число наивных потоков равно кол-ву ядер процессора. А обрабатывать может понадобиться и 1000 одновременных запросов, а то и больше. Вот здесь то и начинаются трудности написания "прямого" кода. С виртуальными потоками такой проблемы нет.
В теории да, на практике нет.
Ждущий поток не стоит почти ничего. 64 килобайта памяти. Тот же jetty на нагруженной апишке отлично живет с пулом в 100 потоков. Это тяжелая апишка с неплохим входящим РПС.
Ну и возможность отскалироваться накликав еще по ядру на каждый шард стоит дорого. Она почти всегда полезнее процента производительности от подгона потоков под ядра.
Виртуальные потоки, файберы, корутины и гопутины - прекрасно зарекомендовали себя в других языках. И в Java необходимость в них давно назрела и перезрела
Полностью согласен. Уже понятно как фичу сделать правильно значит можно ее и в Джаву затаскивать.
Стоило ли это делать 5 лет назад? Не уверен.
А 10 лет назад? Точно нет.
harios
Видится мне что перепись кода будет долгой и сложной для того что бы выгода стала хоть сколько то ощутимой, а результат будет хуже в поддержке и доработке чем оригинал. Так что по моему мнению это сценарий который никогда не произойдет.
sandersru
Скорее простой программист их даже не заметит. Они будут переписаны где то под капотом того же Springboot/Netty/Quarkus/etc
Flowka
не забывайте, что это превью фича и очень многое еще может поменяться. Кроме того, в JEP 425 нет задачи, чтобы код переписанный на virtual threads работал быстрее. Речь идет о замене парадигмы разработки на thread-per-request при минимальном переписывании кода приложения. Что в теории должно сильно упростить разработку и дебаг.