Эта статья участвовала в "Проект «Звезда раскопок»«Выиграйте креативные подарочные наборы и бросьте вызов творческим поощрениям.
Здравствуйте, я криворукий.
Заполните дыру и напишите CompletableFuture, который был причитается ранее, потому что фон получил несколько сообщений с призывом к обновлениям.
Вот что я упомянул в этой статье, которую я написал ранее:«Интервьюер спросил меня, знаю ли я будущее асинхронного программирования»
Поскольку основное внимание уделяется написанию Future, CompletableFuture просто написал его в последнем разделе:
Я просто возьму пример того времени и изменю его, и сначала поставлю код здесь:
public class MainTest {
public static void main(String[] args) throws Exception {
CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "-女神:我开始化妆了,好了我叫你。");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "化妆完毕了。";
}).whenComplete((returnStr, exception) -> {
if (exception == null) {
System.out.println(Thread.currentThread().getName() + returnStr);
} else {
System.out.println(Thread.currentThread().getName() + "女神放你鸽子了。");
exception.printStackTrace();
}
});
System.out.println(Thread.currentThread().getName() + "-等女神化妆的时候可以干点自己的事情。");
Thread.currentThread().join();
}
}
Основное требование состоит в том, что, когда богиня наносит макияж, я могу сначала заняться своими делами.
Результат выполнения вышеуказанной программы выглядит следующим образом:
Соответствует нашим ожиданиям без каких-либо сбоев.
Но когда вы идете писать программу самостоятельно, вы можете столкнуться с такой ситуацией:
Что за ситуация, богиня все еще в гриме, а программа закончена?
Да, это первое, что я скажу о CompletableFuture: потоки демона.
Нить демона
Если вы внимательно посмотрите на два скриншота, упомянутых выше, и сравните их строку 28, на втором скриншоте отсутствует строка кода:
Thread.currentThread().join();
Что делает эта строка кода?
Цель состоит в том, чтобы заблокировать основной поток, даже если вы позволите основному потоку спать, в любом случае цель состоит в том, чтобы заблокировать основной поток.
Без этой строки кода происходит то, что основной поток запускается напрямую, и программа завершается.
Подумайте, в чем может быть причина?
В это время вы должны мысленно щелкнуть и вскоре подумать о понятии «нить демона».
Основной поток — это пользовательский поток, это ни о чем не говорит.
После выполнения всех пользовательских потоков JVM завершает работу.
Следовательно, вышеуказанная проблема возникает потому, что у меня есть основания предполагать: выполнение задач, принадлежащих потокам демона CompletableFuture.
При поддержке теоретических знаний и введении этой гипотезы есть направление на подтверждение, и задача очень проста.
Нажмите здесь точку останова, а затем отладьте ее.Как только выражение будет написано, можно увидеть, что это действительно поток демона:
Обычно я хочу увидеть конкретный код, но мне нужно увидеть строку кода, которая устанавливает этот поток как поток демона, прежде чем я сдаюсь.
Вот я и отправился на поиски, но все равно это заняло немного времени, процесс описывать не буду, а вывод будет прямо изложен.
Прежде всего, пул потоков CompletableFuture по умолчанию — ForkJoinPool, который легко найти в исходном коде:
В ForkJoinPool место, где все потоки устанавливаются как потоки демона, находится здесь:
java.util.concurrent.ForkJoinPool#registerWorker
Если вы хотите отладить свой собственный, то после того, как здесь отмечена точка останова, вы можете посмотреть на стек вызовов и быстро получить четкое представление о вызывающем процессе:
Кроме того, я заметил это примечание во время написания статьи:
Фронт, вероятно, сказал, что shutdown и shutdownNow бесполезны для этого пула потоков.
Если задачи в пуле потоков должны быть выполнены до завершения программы, то перед выходом следует вызвать commonPool().awaitQuiescence.
Итак, моя программа должна быть изменена на это:
Да-да, очень элегантно.
Если ваша асинхронная задача очень важна и должна быть выполнена, то ForkJoinPool также инкапсулирует для вас метод:
java.util.concurrent.ForkJoinPool#quiesceCommonPool
Кроме того, на самом деле CompletableFuture также поддерживает передачу пользовательского пула потоков:
Например, я изменил предыдущую программу на следующую:
Добавьте логику указанного пула потоков, закомментируйте код соединения основного потока и запустите его. Эх, JVM всегда рядом.
Волшебный или нет?
Я думаю, что эта причина не нуждается во мне для анализа, не так ли?
Контраст с будущим
CompletableFuture на самом деле является обновленной версией Future.
В будущем это, у него есть.
Недостатки будущего оно компенсирует.
Ведь один — продукт эпохи JDK 1.5, а другой — произведение эпохи 1.8:
Средний пролет - полные 10 лет, 10 лет!
Итак, подойдите сзади.
Давайте сравним Future и CompletableFuture.
Во-первых, лично для меня первое и самое интуитивное ощущение, что поза получения результатов намного удобнее.
Я должен взять эту картинку, чтобы сказать, что в основном обратите внимание на следующие два Future и Callback:
Когда мы используем Future для достижения асинхронности и получения асинхронного результата, как это работает?
Вам нужно вызвать метод future.get(), чтобы получить значение.
Если значение готово в данный момент и инкапсулировано в будущем, то все в порядке, и его можно использовать напрямую.
Но что, если значение еще не готово?
Ожидание заблокировано?
Поэтому я часто говорю, что будущее является кастрированной версией асинхронизации.
Например, в первом примере, если я использую Future для этого, это выглядит так:
Если вы внимательно посмотрите на то место, которое я обрамил, то основной поток начинает получать результат, а действие получения результата блокирует основной поток.
Ты не можешь мыть голову, братан.
Ну вы же сказали, что ставили операцию, чтобы получить результат до конца, без проблем.
Однако, куда бы вы его ни поместили, у вас есть действие get, и когда вы выполняете это действие, вы не знаете, готово значение или нет, поэтому может возникнуть блокировка ожидания.
Итак, возникает вопрос: что, если это блокирующее ожидание устранено?
Это очень просто, мы изменили наше мышление с активного запроса на ожидание уведомления.
После того, как макияж богини будет готов, вы можете взять на себя инициативу, чтобы сообщить мне, если это не хорошо?
Говоря словами программиста: результат операции отсутствует, можете ли вы просто выполнить функцию обратного вызова, которую я вам оставил?
CompletableFuture может это сделать.
Написание вышеуказанной программы с помощью CompletableFuture выглядит следующим образом:
pool-1-thread-1, эта нить макияжа богини, она возьмет на себя инициативу позвонить вам, когда закончит, понимаете?
Это первое, что заставило меня почувствовать себя комфортно, когда я впервые узнал о CompletableFuture.
Обратите внимание на это написание,whenComplete(returnStr, exception)Информация о возврате и информация об исключении доступны здесь.
Кроме того, этот метод по-прежнему имеет возвращаемое значение, и вы можете получить его возвращаемое значение через get так же, как Future:
Буквально можно использовать.
Но если вам не нужно возвращаемое значение, он также предоставляет такой способ записи:
Нормальные и ненормальные условия обрабатываются отдельно.
Классный, очень классный.
Есть более мясистые.
Разве нить, где мы красились, и нить, где делается макияж, не одна и та же нить:
Предположим, нам нужны два разных потока, один только для макияжа, а другой только для уведомлений. Ведь после того, как богиня накрасилась, она еще больше богиня, и я не могу слишком много думать о двух нитях.
Изменения на удивление малы:
Просто измените вызываемый метод с whenComplete на whenCompleteAysn.
Аналогично, этот метод также поддерживает указание пула потоков:
Вы можете видеть, что в CompletableFuture есть много Aysn-окончаний методов, большинство из которых делают это, и выполняют их в пуле потоков в асинхронной форме.
Если приведенное выше описание заставляет вас чувствовать себя намного лучше, чем это, то повторно введите что-то, чего нет в Future.
Допустим, сейчас так.
После того, как богиня накрасилась, выбор одежды занял немного времени, а это не так уж и много.
То есть у нас теперь есть две асинхронные задачи, первая — накраситься, а вторая — выбрать одежду.
Выбор одежды должен быть сделан после завершения макияжа.Эти две задачи сериализуются.Как этого добиться с помощью CompletableFuture?
Я опубликую код, чтобы сделать его более интуитивным, я не использовал цепные вызовы:
public class MainTest {
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newFixedThreadPool(10);
//任务一
CompletableFuture<String> makeUpFuture = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "-女神:我开始化妆了。");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "化妆完毕了。";
}, executorService);
//任务二(makeUpFuture是方法调用方,意思是等makeUpFuture执行完成后执行再执行)
CompletableFuture<String> dressFuture = makeUpFuture.thenApply(makeUp -> {
System.out.println(Thread.currentThread().getName() + "-女神:" + makeUp + "我开始选衣服啦,好了我叫你。");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return makeUp + "衣服也选好啦。靓仔,走去玩儿吧。";
});
//获取结果
dressFuture.thenAccept(result -> {
System.out.println(Thread.currentThread().getName() + "-" + result);
});
}
}
Вывод такой:
В соответствии с нашими ожиданиями.
Что, если мы переключимся на другой поток, когда хотим выбрать одежду?
Не говорите, что не знаете, разве это не научило вас только что, метод в конце Async должен быть изучен и использован:
Мы говорили о последовательном выполнении нескольких асинхронных задач, а затем поговорим о параллелизме.
CompletableFuture предоставляет два параллельных метода:
Входными параметрами двух методов являются переменные параметры, представляющие собой асинхронные задачи.
allOf Как следует из названия, несколько введенных CompletableFuture должны быть успешными, чтобы продолжить выполнение.
А anyOf — это несколько CompletableFuture, которые вводятся до тех пор, пока один из них завершается успешно.
Еще пример.
Предположим, я имею в виду предположим, что я морской король.
Забудь об этом, я предполагаю, что у меня есть друг.
Он преследует несколько подруг одновременно. Сегодня он планирует пригласить кого-нибудь из Сяомей и Сяоцзяо пойти поиграть, кто бы из них ни согласился. У того, кто сделает макияж первым, будет назначена встреча. Другой просто позволил ей нырнуть.
В этом сценарии мы можем использовать anyOf для моделирования, поэтому появляется этот код:
Судя по результатам вывода, Сяомей была последней, кто встречался со своей подругой.
Они пригласили Сяомей на свидание, и мы должны пообедать вместе, верно?
Затем в это время мой друг спросил: Сяомей, что бы ты хотел поесть?
Сяомей обязательно ответит: Что угодно, ничего страшного, неважно.
Услышав такой ответ, мой друг понял суть, и сразу же дал план: идем есть шаксианские закуски или желтую тушеную курицу, в каком бы ресторане не было короткого времени ожидания, в какой мы и пойдем.
Таким образом, приведенный выше код становится таким:
Вывод таков:
Я разместил весь код здесь, вы можете вставить его, и он запустится:
public class MainTest {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
CompletableFuture<String> xiaoMei = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "-小美:我开始化妆了,好了我叫你。");
try {
int time = ThreadLocalRandom.current().nextInt(5);
TimeUnit.SECONDS.sleep(time);
System.out.println(Thread.currentThread().getName() + "-小美,化妆耗时:" + time);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "小美:化妆完毕了。";
}, executorService);
CompletableFuture<String> xiaoGuai = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "-小乖:我开始化妆了,好了我叫你。");
try {
int time = ThreadLocalRandom.current().nextInt(5);
TimeUnit.SECONDS.sleep(time);
System.out.println(Thread.currentThread().getName() + "-小乖,化妆耗时:" + time);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "小乖:化妆完毕了。";
}, executorService);
CompletableFuture<Object> girl = CompletableFuture.anyOf(xiaoMei, xiaoGuai);
girl.thenAccept(result -> {
System.out.println("我看最后是谁先画完呢 = " + result);
});
CompletableFuture<String> eatChooseOne = girl.thenApplyAsync((result) -> {
try {
TimeUnit.SECONDS.sleep(ThreadLocalRandom.current().nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
return result + "这里人少,我们去吃沙县小吃吧!";
}, executorService);
CompletableFuture<String> eatChooseTwo = girl.thenApplyAsync((result) -> {
try {
TimeUnit.SECONDS.sleep(ThreadLocalRandom.current().nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
return result + "这里人少,我们去吃黄焖鸡吧!";
}, executorService);
CompletableFuture.allOf(eatChooseOne, eatChooseTwo).thenAccept(result -> {
System.out.println("最终结果:" + result);
});
}
}
Если вы говорите, что выбор делают только дети, взрослые хотят всего.
Затем вы можете попробовать использовать allOf, просто обратите внимание, что allOf не возвращает значение.
Что ж, пишу здесь, я чувствую себя немного похожим на обучение API, не очень весело. Итак, CompletableFuture имеет много-много методов, поэтому я не буду представлять их по одному.
Поговорим о методе get
Наконец, давайте посмотрим на метод get. ранее опубликованный«Прочитав эту проблему с производительностью исходного кода параллельного пакета JDK, я был в шоке! 》Эта статья, у друга есть несколько вопросов после прочтения, я расскажу об этом снова.
CompletableFuture отправляет задачи двумя способами:
Один из них SupplyAsync с возвращаемым значением.
Во-первых, возвращаемое значение runAsync равно void, что эквивалентно отсутствию возвращаемого значения.
Например, когда мы используем SupplyAsync:
Намеренно вернуть ноль.
Я также могу расширить, скажем, наш метод использует runAsync, который не имеет возвращаемого значения.
Например:
Давайте еще раз посмотрим на метод get:
Видите ли, условие суждения здесь такое.(r = result) == null.
Тогда возникает вопрос: предположим, что возвращаемое значение этого метода равно нулю, как и в нашей ситуации выше, что нам делать?
Есть три случая для null:
- 1. Это runAsync, который действительно не имеет возвращаемого значения, поэтому даже если выполнение задачи завершено, результат получения действительно нулевой.
- 2. Есть возвращаемое значение, но задача еще не выполнена, поэтому результат по-прежнему нулевой.
- 3. Есть возвращаемое значение, и возвращаемое значение равно null.
Как отличить эти три ситуации?
Затем мы должны посмотреть на место, где присвоен результат.Угадав пальцами ног, вы знаете, что вы здесь что-то сделали.
Итак, после простого поиска вы можете найти это ключевое место:
Цель упакованного кода — получить смещение поля результата в классе CompletableFuture и сохранить его с RESULT в верхнем регистре.
Опытные друзья наверняка знают, что здесь используется операция compareAndSwapObject:
Затем вы можете найти эти места, связанные с нулевым значением:
Ответ - это часть, которую я обрамил:В CompletableFuture null также инкапсулируется в объект AltResult.
Исходя из этого, можно выделить три случая, о которых я упоминал ранее.
Вы видите, что есть специальный метод completeNull, у вызывающего объекта которого есть метод AysncRun:
Вы можете поставить точку останова там, где она вызывается, а затем запустить код, который я отправил ранее, с помощью runAsync:
Взгляните еще раз на стек вызовов, отладьте его, и вы поймете, что runAsync действительно не имеет возвращаемого значения.
Основная технология заключается в инкапсуляции null в объект AltResult.
Тогда как вернуть случай, когда значение равно null?
Есть объект, представляющий null, не так ли просто, делается небольшое суждение:
Наконец, позвольте мне упомянуть этот метод:
java.util.concurrent.CompletableFuture#waitingGet
В моей предыдущей статье я написал это предложение:
Это вращение добавляется для выполнения кода парковки в последующей логике позже, что является немного более сложной операцией. Но я думаю, что преимущества этого «краткого ожидания» на самом деле минимальны.
Некоторые друзья спросили меня, где логика парка?
На самом деле, это последняя ветвь цикла while ожиданияGet, которую я обрамил:
Наконец, вы можете найти это место, перейдя по Debug:
java.util.concurrent.CompletableFuture.Signaller#block
Разве это не логика парка:
Хит точку останова и играть самостоятельно.
На самом деле есть еще одна дерзкая операция, о которой я обычно никому не рассказываю, а просто делюсь.
Или возьмем предыдущий код в качестве демонстрации.После того как вы запустите этот код, основной поток заблокируется в ожидании результата асинхронной задачи, потому что вызывается метод get:
Вы просто запускаете его и нажимаете на значок камеры:
Вы можете увидеть этот экран:
Основная нить парк вместе, в котором парк это?
at java.util.concurrent.CompletableFuture$Signaller.block(CompletableFuture.java:1707)
Разве не так я только что сказал вам?
Затем вы ставите здесь точку останова, смотрите на стек вызовов и четко воспроизводите основную ссылку:
Как эта волна обратного действия, скольжение или скольжение, узнаете за считанные минуты.
Нашли место для парковки, а где оно было незапаркованным?
Разве это не просто?
Во всяком случае, я искал это в поиске:
Затем ударил точку останова здесь в нечетко:
Процесс пробуждения также можно четко отладить.
Что ж, приостановка и пробуждение привели вас к ключевому месту, поехали, поиграем.
Эта статья была включена в мой личный блог, играть может каждый.