Комментарии 50
А у меня Java 8. И так будет еще долго.
Все дело в том, что оракл (выпустив Java 9) поменял свою политику выпуска релизов, и эта новая политика частых релизов многих не устраивает на самом деле. Новые релизы нужны тем, кто делает новые проекты, а тем кто эксплуатирует старые много лет — нужна стабильность и совместимость. А они ее сломали. Это был пожалуй единственный релиз, который сломал например maven и gradle. Хотя уж казалось бы, там-то что можно сломать? Даже Java 5 не вызывала таких проблем при миграции (я в своей практике мигрировал раз пять, а считая JavaEE — еще больше).
Это Jigsaw, класс Unsafe и Reflection, чтобы не получали доступ к тому, что не было запроектировано.
Проект Jigsaw ломает доступ к использованию непубличного API. Поэтому сравнение с Java SE 5 некоректно. Когда получилось проект влить в релиз — тогда и сделали. Так совпало, что всё это попало в Java SE 9, а могли и в 8 уже всё это увидеть при определённой развитии истории.
Стабильность это LTS: Java SE 8, 11, 17. Все другие выпуски проходные и мне непонятно зачем крутить счётчик 2 раза в год. Зря отказались от версионирования как в Ubuntu.
>Это Jigsaw, класс Unsafe и Reflection, чтобы не получали доступ к тому, что не было >запроектировано.
Не уверен, что gradle и мавену это все нужно. Скорее уж сломали что-то в класслоадерах (и даже примерно понятно, что именно).
>Проект Jigsaw ломает доступ к использованию непубличного API. Поэтому сравнение с Java SE 5 некоректно.
Сравнение тут затем, что даже сложный переход на generics не стоил когда-то таких усилий (ну, это мои впечатления, если что). И судя по ним же, никогда столько народу не оставалось сидеть на старых версиях, как после выхода Java 9. Когда это получилось — уже не так важно. Важно что это решение сломало обратную совместимость, с которой до этого все было сильно получше.
Ну то есть, я почему maven и gradle упомянул — потому что до этого никогда релизы не ломали такие базовые вещи, как системы сборки — идеологически достаточно простые, в общем-то. А тут сумели. А уж если говорить о проектах посложнее…
Вот оно в 2017 вышло — а до сих пор скажем Hadoop не может завершить миграцию как следует. И какой вывод напрашивается? Что на пользователей хадупа при выпуске 9 наплевали. Что ораклу нужно было решить свои проблемы. Ну вот они наплевали и решили — теперь и имеем то что имеем. Какая там доля Java 8 на сегодня, не напомните?
Видите ли вы применение Project Loom в своем проекте?
Голосование не совсем корректное, так как отсутствует мультивыбор.
В частности, для проекта с Kotlin и Oracle + JDBC имеет смысл протестировать loom. Oracle не планирует уже делать неблокирующий интерфейс, так что только loom и спасет.
Далее, если loom окажется быстрее корутин (а для этого есть все шансы, так как он, например, выделяет меньше памяти, плюс обрабатывает исключения нативно), то вообще имеет смысл уходить от условного ktor client на старый добрый Apache Client, который автоматом станет неблокирующим. Заодно можно получить потоковую десериализацию.
А почему сейчас нельзя взять вам AsyncHttpClient или WebClient
Они оба неблокирующие. Как и ktor.
После loom у них у всех трех будет одна общая проблема: все создают ненужные объекты в куче, т.е. CompletableFuture, Mono, Flux, Flow и так далее. Плюс, ради проброса исключений, у них есть код, который "восстанавливает callstack", который тоже требует ресурсов. Плюс улетают JVM оптимизации вида "выделяем объект на стеке, так как он не возвращается наружу никогда" и море других похожих.
Кстати, Spring Flux тоже будет не особо нужен, так как он выделяет море лишних объектов в куче ради асинхронности. Вот пример такой проблемы (она осталась, просто не видна), вот статья про неё на хабре.
Servlet 4.0 (см. 2.3.3.3) тоже содержит неблокирующие опции, от которых надо будет избавляться, как от ненужных.
Netty весь построен на колбеках, что будет не нужно.
В общем, Loom полезный проект. Однако из-за него, для более эффективной работы, надо будет переписать много чего, чтобы избавиться от текущей модели явной асинхронности и начать использовать неявную. А значит и потребуются правки в коде приложений.
То что вы написали это все правда. Вопрос был в другом зачем уходить на
то вообще имеет смысл уходить от условного ktor client на старый добрый Apache Client
когда описанные асинхронные клиенты удобней в плане работы с АПИ.
Я часто видел стектрейсы гораздо длинней на обыкновенной джаве :)))
Вот пример такой проблемы (она осталась, просто не видна)
for (int i=0;i<1000_000;i++)
tasks.add(()->/* do something */);
for (int i=0;i<tasks.size();i++)
task.get(i).run();
Ради чего расширяется набор базовых библиотек???
Не знаю, что конкретно сочинили в Java15, но «переключиться на другой таск» означает ровно всё то же самое, что и переключиться на другой поток, а значит нет главного — никаких отличий от старого.
Хотя опять же есть асинхронное IO. Если мне нужно «переключаться на другой таск», то я сам укажу программе где и как я хочу переключиться, просто использовав что-то вроде пакета nio. Соответственно — зачем мне нововведение, где что-то действует неконтролируемым образом?
По сути вопрос о внутреннем устройстве нововведения. Если оно известно — можно что-то обсуждать. Если нет — просто нечем обосновывать ценность нововведения. Мне неизвестно, думал автор статьи пояснит, но он отмалчивается. Значит придётся ждать более вменяемых описаний.
Внутреннее устройство нововведения уже было на Хабре ранее. Раздача халявы: нетормозящие треды в Java. Project Loom
Но тем не менее всё так же непонятно, с чего автор обсуждаемой здесь статьи (уже не по вашей ссылке) считает, что в подобной реализации возможно какое-то другое вытеснение псевдо-потоков кроме кооперативного. Судя по данным по вашей ссылке никакого другого подхода там быть не может (кроме кооперации и неявного переключения в некоторых избранных местах, в виде нативных вызовов, которые и реализуют переключение). Может автору тоже стоит познакомиться с материалом по ссылке.
А в чём проблема кооперативного вытеснения?
Потоки загружают ядра. Если убрать переключение потоков — ядра будут недогружены. Кооперация гарантирует, что переключение во многих случаях работать перестанет из-за множества проблем на стороне писателей в кооперативном стиле.
Но и вам вопрос — а в чём преимущества кооперации потоков?
С чего бы ядра будут недогружены-то?
Два уровня же. Виртуальные треды проецируются на процессорные. Вот с загруской процессорных какраз пока особо ничего не меняется, кроме того, что им потенциально будет больше поделать а не меньше.
Если вы про VirtualThread то, как-бы очевидно же (покрайней мере мне) что блокирующий код легче читать/писать… не нужно лишних нагромождений а иммено это опять будет возможно не теряя все плюшек многопоточности.
Хотя да, общие слова — это наше всё. Так что я обязательно поставлю галку «зачтено» в виртуальном журнале против ваших ников.
Ну и так, напоследок:
for (int i=0;i<1000_000;i++)
tasks.add(()->/* do something */);
for (int i=0;i<tasks.size();i++)
task.get(i).run();
И два:
for (int i=0;i<1000_000;i++)
{ /* do something */ }
Что по вашему проще?
Смотрите если в tasks у вас обычные треды вы там с блокирующими операциями просядите. Это понятно?
С виртуальными тредами не просядите, потому что когда до операции ввода/вывода дойтете то виртуальный тред хоть и будет ждать, но реальный не будет простаивать.
Тем самым вы упрощаете код который у вас:
/* do something */
там вы можете в лоб теперь блокирующе читать файлы или сокет… 100 000 раз. и все это без колбэков в коде. У меня нет под рукой сейчас примера с колбэком и без. Влом выдумывать.
а то что вы показали останется тем же.
вы там с блокирующими операциями просядите
Дело в том, что альтернатива, во первых (а это ошибки), неявно, а во вторых, для очень ограниченного количества действий, расшивает указанную вами проблему. Из этого следует, что каждый раз программист будет думать, долго и без гарантий правильности. В итоге будут все те же ошибки, что и с обычными потоками, плюс дополнительно проблемы из-за специфики псевдо-потоков. В сумме = хуже.
Ну и помимо «общечеловеческих ценностей» есть ещё и неблокирующее IO API. И что мешает просто взять и его использовать? Не наступая на грабли с новыми псевдо-потоками. Ведь вы именно на асинхронное IO упираете, доказывая ценность псевдо-потоков. И вот оно, асинхронное IO, и так есть, и давно используется, ну и без приставки псевдо.
неблокирующее IO API. И что мешает просто взять и его использовать?
Используйте. Вам никто не мешает.
Но неблокирующее какраз больше кода, об этом же и пишу вам. Сложнее ментальная модель, сложнее в дебаге.
С новыми тредами вам это не нужно будет во многих случаях. Тоесь вы просто можете забитъ на ассинхронне апи, БЕЗ особого усложнения понятия тред. Вы почему-то пытаетесь доказать что треды будут сложнее. На мой же взгляд разница помойму очень проста, еще один уровень абстракции как в Го с сохранением АПИ.
Может у вас свой специфический контектс и там все так как вы говорите — тогда это частный случай скорее.
ИМХО мне кажется это очень правильное направление и упросит в будушем многое. 16 ядрами сейчас никого не удивишь к примеру. С лекговесными треадами и простым блокирующим кодом их будет проще запользовать — это правильное направление ИМХО.
это правильное направление
Правильное направление — это наконец-то понять, что асинхронный код существует уже много лет и есть нетривиальное количество примеров и подходов, которое позволяет примерно оценить наиболее эффективное направление.
А то, что сейчас джависты идут по пути, который, скажем, JavaScript комьюнити уже прошло N-лет назад (callback hell -> promises hell -> control flow streams\...), наступая по пути на все те же грабли просто потому что «мы привыкли писать синхронный блокирующий код но это нормально не работает на 32ядерном тред-риппере, давайте закостылим!». Просто смешно.
Если бы это была какая-то революция — еще можно было понять. Сейчас асинхронный конкурентный код — это стандарт для высоконагруженного бэкенда.
Я не против появления грин-тредов, но при их проектировании надо учитывать реальный контекст применения, а не пытаться спасти динозавров.
Я не против появления грин-тредов, но при их проектировании надо учитывать реальный контекст применения, а не пытаться спасти динозавров.
дайте хоть пример того что не учтено?
П.С. Смотрите на Golang. Я лично не понимаю пока вашей аргументации. Мне кажется вы противоречите себе.
Но фронтендеры, привыкшие обрабатывать нажатие мышки по кнопочке, не понимают как написать многопоточную программу, вот и лепят свою лапшу из коллбэков в свою node.js.
Скажите, а что вам, настоящим бэкендерам, мешает обрабатывать асинхронные задачи многопоточно?
Kestrel вот почти без усилий держит 10 000 одновременных HTTP соединений, мы в феврале это выяснили. А многопоточный сервер в тех же условиях умирает.
Скажите, а что вам, настоящим бэкендерам, мешает обрабатывать асинхронные задачи многопоточно?
Ну обычно в защиту лапши из коллбэков приводят аргумент мол ненадо синхронизироваться, а если их обрабатывать многопоточно — синхронизироваться все-таки надо, ну и теряется смысл такой системы
Но фронтендеры, привыкшие обрабатывать нажатие мышки по кнопочке, не понимают как написать многопоточную программу, вот и лепят свою лапшу из коллбэков в свою node.js.
Звучит как какая-то обида на фронтенд…
callback hell и т.д.
Что значит «и т.д.»? Callback hell — это были первые попытки структурировать асинхронный код. С тех пор мир уже ушел далеко вперед и есть гораздо более эффективные и элегантные решения о чем я и писал в своем комментарии.
То в современных реалиях это жутко неффективно и тотально проигрывает нормальным тредам на современном 32-х ядерном тредрипере. Потому что треды на таком тредрипере выполняются действительно параллельно.
100%. Поэтому я и говорю, что для высоконагруженной программы нужно писать асинхронный + конкурентный код. Это позволит добиться наилучших результатов при использовании ограниченных вычислительных ресурсов
Project Loom как раз пытается хотя бы частично решить именно те проблемы, о которых Вы пишете. Эти проблемы есть во всех языках, поддерживающих синхронные и асинхронные вызовы. И пока совсем идеального решения никто не нашел.
Java — это не про революцию. И это очень хорошо.
Тут у некоторых людей проблемы с переездом с 8-ки из-за добавления микросекунд в Instant.now(), а Вы про какую-то революцию говорите.
Ну и я совсем не понял, почему Вы ставите знак равенства между «асинхронность» и «высокая нагрузка», не поясняя, что имеется в виду под «нагрузка».
Ну и я совсем не понял, почему Вы ставите знак равенства между «асинхронность» и «высокая нагрузка», не поясняя, что имеется в виду под «нагрузка».
Не совсем так. Я пытался сказать, что асинхронный конкурентный код — это самый эффективный способ использования вычислительных ресурсов, а значит — позволяет наиболее эффективно (с точки зрения потребления ресурсов) обрабатывать высокие нагрузки. Под нагрузками я имею ввиду то, что подразумевает термин highload.
Асинхронный == треды не простаивают впустую.
Конкурентный == все ядра CPU используются по максимуму.
а Вы про какую-то революцию говорите
Я про эволюцию. Чтобы подходы, которые имели место быть 10-15-20 лет назад заменялись подходами, которые актуальны сейчас.
Посмотрите пожалуйста статью What Color is Your Function?
Почитал, но что-то полезное оттуда вынести сложно. Как я выше написал — люди уже пережили callback-hell и прочие грабли с промисами. Уже есть адекватные механизмы построения control-flow Вашей асинхронной программы. Статья 2015 года, поэтому неудивительно, что автор не упоминает практически ни одного из них. Думаю, что в то время они были не особо распространены. Сейчас фактически все перечисленное там — не актуально.
Project Loom как раз пытается хотя бы частично решить именно те проблемы, о которых Вы пишете
Еще раз — я двумя руками за грин треды и прочие решения этих проблем. Но очень хотелось бы, чтобы это не было в стиле — «я обновил версию JVM и о чудо мой блокирующий код стал не блокирующим». Потому что это не сработает. По крайней мере в Java-мире.
Но очень хотелось бы, чтобы это не было в стиле — «я обновил версию JVM и о чудо мой блокирующий код стал не блокирующим»
Project Loom этого и не предлагает. Код остается таким же каким и был и по-прежнему блокируется, просто снижаются затраты ресурсов на поддержку и переключение тредов, соответственно их можно запустить больше и не волноваться о том, что запуск нового треда как-то затратен по ресурсам. Поэтому, например, пропадает смысл долгоживущих тредов, обрабатывающих очередь каких-то задач, можно запускать сразу все задачи в своих отдельных тредах. Это уже даже некое упрощение в сравнении с существующими подходами.
Знаком не только с вышеприведенной статьей, но так же с оригинальным выступлением:
https://www.youtube.com/watch?v=J31o0ZMQEnI
Как можно видеть, графики и объяснение как работает развертывание стека были взяты как раз оттуда.
У том, что с Project Loom возможен и forced preemtion Ron Pressler писал мне тут:
https://twitter.com/pressron/status/1262350580820869120
И так же вскользь упоминает об этом в State of Loom:
For this reason, none of the schedulers in the JDK currently employs time-slice-based preemption of virtual threads, but that is not to say it won’t in the future — see Forced Preemption.
В целом стремление впихнуть в абстракцию потока миллионы экземпляров точно так же чревато адской неэффективностью. Даже если памяти на псевдо-поток будет тратиться поменьше, дальнейшая обработка никак из штанов выпрыгнуть не сможет, ну и вся затея будет иметь смысл в очень и очень ограниченных нишах. Могу себе представить разве что какой-то самый примитивный роутер, распихивающий потенциально очень большие флуктуации на входе по вот таким недо-потокам, которые в итоге будут служить лишь цели усложнения архитектурного шаблона «очередь» (и памяти потреблять, разумеется, больше, чем оптимизированная очередь в памяти).
Хотя возможно вы можете представить какое-то более разумное применение для сабжа?
Начать стоит с того, что вышеприведенный код запустится в одном потоке.
Ведь это по факту копирует работу планировщика системы — не перечеркивает ли это все плюсы корутин?
Не перечеркивает. Из достоинств корутин стоит отметить динамический размер стека и более быстрый context switch. Так что иметь forced preemtion — полезная вещь, вот только Go в этом плане всегда ударяется в крайности. Либо preemtion для всех, либо ни для кого.
вот эти все модные корутины похожи на реализацию кооперативной многопоточности.
неа
Virtual threads are preemptive, not cooperative — they do not have an explicit await operation at scheduling (task-switching) points
Хорошо, что хотя бы от Fiber терминологии избавились. А был же еще Strand.
Ну они не могут просто так поменять конктракт. И правильно.
На самом деле очень элегантно врезали новую фичу на мой вкус. Вы и сами пишите:
с точки зрения разработчика поменяется только то что, что надо будет теперь везде в коде поменять thread на virtual thread.
только можно и не менять или если то достаточно мало и без риска
Project Loom: виртуальные потоки в Java уже близко