Использует ли код с CompletableFutures и без пользовательских исполнителей только количество потоков, равное количеству ядер? - PullRequest
0 голосов
/ 10 июня 2018

Я читаю Java 8 в действии, глава 11 (около CompletableFuture с), и это заставило меня задуматься о кодовой базе моей компании.

Книга Java в действии говорит, что если у вас есть кодкак я запишу ниже, вы будете использовать только 4 CompletableFuture с за раз (если у вас 4-х ядерный компьютер).Это означает, что если вы хотите выполнить, например, 10 операций асинхронно, вы сначала запустите первые 4 CompletableFuture s, затем вторые 4, а затем 2 оставшиеся, потому что по умолчанию ForkJoinPool.commonPool() предоставляет только количество потоков.равно Runtime.getRuntime().availableProcessors().

В кодовой базе моей компании есть @Service классы, называемые AsyncHelper s, которые содержат метод load(), который использует CompletableFuture s для загрузки информации о продукте.асинхронно в отдельных кусках.Мне было интересно, используют ли они только 4 потока одновременно.

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

Было принято архитектурное решение загрузить детали страницы pdp порциями,Предполагается, что загрузка происходит асинхронно, и текущий код использует CompletableFuture с.Давайте посмотрим на псевдокод:

static PdpDto load(String productId) {
    CompletableFuture<Details> photoFuture =
            CompletableFuture.supplyAsync(() -> loadPhotoDetails(productId));
    CompletableFuture<Details> characteristicsFuture =
            CompletableFuture.supplyAsync(() -> loadCharacteristics(productId));
    CompletableFuture<Details> variations =
            CompletableFuture.supplyAsync(() -> loadVariations(productId));

    // ... many more futures

    try {
        return new PdpDto( // construct Dto that will combine all Details objects into one
                photoFuture.get(),
                characteristicsFuture.get(),
                variations.get(),
                // .. many more future.get()s
        );
    } catch (ExecutionException|InterruptedException e) {
        return new PdpDto(); // something went wrong, return an empty DTO
    }
}

Как вы можете видеть, в приведенном выше коде нет пользовательских исполнителей.

Означает ли это, что если этот метод загрузки имеет 10 CompletableFuture s ив настоящее время 2 человека загружают страницу PDP, и у нас есть всего 20 CompletableFuture с для загрузки, тогда все эти 20 CompletableFuture с не будут выполняться одновременно, а только 4 одновременно?

Мой коллега сказал мне, что каждый пользователь получит 4 потока, но я думаю, что JavaDoc довольно четко заявляет это:

public static ForkJoinPool commonPool () Возвращает экземпляр общего пула.Этот пул статически построен;на его состояние выполнения не влияют попытки shutdown () или shutdownNow ().Однако этот пул и любая текущая обработка автоматически завершаются по программе System.exit (int).Любая программа, использующая асинхронную обработку задач для завершения до ее завершения, должна вызывать commonPool (). AwaitQuiescence перед выходом.

Это означает, что для всех пользователей нашего веб-сайта имеется только 1 пул с 4 потоками.

Ответы [ 2 ]

0 голосов
/ 11 июня 2018

В базе кода моей компании есть [...] классы, [...] содержащие метод load (), который использует CompletableFutures для загрузки информации [...]

Итак, вы говорите, что load() метод ожидает завершения ввода / вывода?

Если так, и если то, что говорит @Bohemian, верно, то вам не следуетиспользовать пул потоков по умолчанию.

@ Богемиан говорит, что в пуле по умолчанию примерно столько же потоков, сколько у вашего хоста с процессорами.Это замечательно, если ваше приложение имеет много задач с привязкой к вычислениям для выполнения в фоновом режиме.Но это не так здорово, если в вашем приложении много потоков, ожидающих ответов от разных сетевых сервисов.Это совсем другая история.

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

0 голосов
/ 11 июня 2018

Да, но это еще хуже ...

Размер общего пула по умолчанию 1 меньше количества процессоров / ядер (или 1, если имеется только 1 процессор), то есть вы фактически обрабатываете 3 за раз, а не 4.

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

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

...