TL; DR Здесь правильно использовать thenCompose
вместо thenComposeAsync
, но не по указанным причинам. Как правило, пример кода не следует использовать в качестве шаблона для вашего собственного кода.
Эта глава является повторяющимся топом c на Stackoverflow по причинам, которые мы можем лучше всего описать как «недостаточное качество», чтобы оставаться вежливым.
В общем, метод без Asyn c суффикс в своем имени выполняет свою задачу в тех же потоках, что и предыдущая задача,…
В спецификации нет такой гарантии относительно выполняющегося потока. В документации говорится:
- Действия, предоставляемые для зависимых завершений неасинхронных c методов, могут выполняться потоком, завершающим current CompletableFuture или любым другим вызывающим методом завершения.
Таким образом, существует также вероятность того, что задача выполняется «любым другим вызывающим методом завершения». Интуитивно понятный пример:
CompletableFuture<X> f = CompletableFuture.supplyAsync(() -> foo())
.thenApply(f -> f.bar());
Здесь задействованы два потока. Один вызывает supplyAsync
и thenApply
, а другой - foo()
. Если второй завершает вызов foo()
до того, как первый поток входит в выполнение thenApply
, возможно, что future уже завершено.
Future не помнит, какой поток его завершил. Также у него нет какой-то магической c способности указать этому потоку выполнить действие, несмотря на то, что он мог быть занят чем-то другим или даже с тех пор завершился. Таким образом, должно быть очевидно, что вызов thenApply
в уже завершенном будущем не может обещать использовать поток, который его завершил. В большинстве случаев действие выполняется немедленно в потоке, вызывающем thenApply
. Это покрывается формулировкой спецификации « любой другой вызывающий метод завершения ».
Но это еще не конец истории. Как этот ответ объясняет, когда задействовано более двух потоков, действие также может быть выполнено другим потоком, одновременно вызывающим не связанный метод завершения в будущем. Это может происходить редко, но это возможно в эталонной реализации и разрешено спецификацией.
Мы можем резюмировать это как: Методы без Asyn c обеспечивают наименьший контроль над потоком, который выполнит действие и может даже выполнить его прямо в вызывающем потоке, что приведет к синхронному поведению.
Так что они лучше всего, когда выполняющийся поток не имеет значения, и вы не надеетесь для выполнения фонового потока, то есть для коротких неблокирующих операций.
, тогда как метод, завершающийся с помощью Asyn c, всегда отправляет последующую задачу в пул потоков, поэтому каждая из задач может быть обработана другой веткой. В этом случае результат второго CompletableFuture зависит от первого,…
Когда вы выполняете
future.thenCompose(quote ->
CompletableFuture.supplyAsync(() -> Discount.applyDiscount(quote), executor))
, задействованы три фьючерсы, поэтому не совсем понятно, какое будущее подразумевается под «вторым». supplyAsync
отправляет действие и возвращает будущее. Отправка содержится в функции, переданной в thenCompose
, которая вернет другое будущее.
Если вы использовали здесь thenComposeAsync
, вы только указали, что выполнение supplyAsync
должно быть отправлено в поток pool, вместо того, чтобы выполнять его непосредственно в завершающем потоке или «любом другом вызывающем методе завершения», например, непосредственно в потоке, вызывающем thenCompose
.
Рассуждения о зависимостях здесь не имеют смысла. «, затем » всегда подразумевает зависимость. Если вы используете здесь thenComposeAsync
, вы принудительно отправили действие в пул потоков, но эта отправка все равно не произойдет до завершения future
. И если future
завершено в исключительных случаях, отправки вообще не будет.
Итак, разумно ли здесь использовать thenCompose
? Да, но цитата не по причинам, указанным. Как уже говорилось, использование метода non-asyn c подразумевает отказ от контроля над выполняющимся потоком и должно использоваться только тогда, когда поток не имеет значения, особенно для коротких неблокирующих действий. Вызов supplyAsync
- дешевое действие, которое само по себе отправит фактическое действие в пул потоков, поэтому его можно выполнять в любом потоке, который свободен для этого.
Однако это ненужное осложнение. Вы можете добиться того же, используя
future.thenApplyAsync(quote -> Discount.applyDiscount(quote), executor)
, который будет делать то же самое, отправить applyDiscount
на executor
, когда future
будет завершен, и создать новое будущее, представляющее результат. Использование комбинации thenCompose
и supplyAsync
здесь не требуется.
Обратите внимание, что именно этот пример уже обсуждался в этих вопросах и ответах , который также решает ненужное разделение будущих операций по нескольким Stream
операциям, а также неправильная диаграмма последовательности.