#java #multithreading #runnable
#java #многопоточность #runnable
Вопрос:
У меня есть программа, которая предназначена для многопоточности. У меня есть ProcessRunnable
класс, который обрабатывает данные с большим количеством требуемых операций ввода-вывода. ProcessRunnable
Все классы выполняются в отдельных потоках, но создаются с помощью общих экземпляров клиентских / util-классов.
Пример:
Client client = new Client();
Util util = new Util();
List<Runnable> runnables = new ArrayList<>();
for (int i; i < THREAD_COUNT; i ) {
runnables.add(ProcessRunnable
.builder()
.client(client)
.util(util)
.build());
}
runnables.forEach(runnable -> new Thread(runnable).start());
Мне любопытно, блокирует ли повторное использование одних и тех же экземпляров классов в runnables поведение и, по сути, приводит к тому, что моя программа становится однопоточной?
Комментарии:
1. ^ мило^ спасибо @AniketSahrawat
2. @GhostCat достаточно справедливо — очки получены! Спасибо, что вступили в обсуждение, несмотря на лучшие практики 😉
3. Не беспокойтесь — еще раз спасибо!
4. Добро пожаловать. И, пожалуйста, не забывайте об удалении ненужных комментариев.
Ответ №1:
Здесь:
runnable -> new Thread(runnable).start()
Ключевой момент, который фактически делает ваш код многопоточным, заключается в том, что вы вызываете метод start() ваших объектов потока. Если бы вы просто вызвали метод run класса thread, то в конечном итоге «заключающий» поток выполнял бы всю работу.
Наконец, пожалуйста, обратите внимание, что прямое использование «голых» потоков не идеально. Об этом можно узнать, но Java предлагает важные абстракции, такие как ExecutorService, которые следует использовать вместо этого по разным причинам.
Основная причина избегать необработанных потоков: вы должны контролировать все тонкие детали вручную. Сколько потоков следует использовать? Как насчет объединения потоков и совместного использования (создание потока сопряжено с большими накладными расходами, поэтому в реальном мире вы избегаете создания потоков для отдельных задач, чтобы затем отбросить их, как это делает ваш код). Помимо этого: обычно вы хотите решить бизнес-проблему. Вы хотите использовать несколько потоков, чтобы предотвратить ситуации с бутылочным горлышком. Пример: вы хотите сделать несколько запросов по сети параллельно для извлечения и обработки данных. Тогда вас действительно волнует только конечный результат, а не тонкости потоковой обработки низкого уровня! Тогда вы могли бы, например, использовать объекты Future или CompleteableFuture.
Просто воспользуйтесь поисковой системой и изучите эти термины, вы найдете множество материалов.
Комментарии:
1. спасибо за ваш комментарий! Не могли бы вы подробнее рассказать о преимуществах, предоставляемых использованием ExecutorService, и / или порекомендовать какой-либо материал для чтения, который, по вашему мнению, особенно полезен? Потоки разделяют состояние через BlockingQueues / BlockingDeques — Я обнаружил, что относительно просто управлять потоками / завершать потоки соответствующим образом на основе BlocksQueues, но, конечно, хотелось бы понять лучший способ выполнения действий!
2. Приведенный выше код упрощен до объема исходного вопроса. Используемые потоки не выбрасываются, а выполняются долго и извлекают из PriorityBlockingQueue по одному элементу за раз для обработки. Я определенно вижу, как это можно улучшить, используя ExecutorService для управления потоками. В настоящее время все этапы обработки инкапсулированы в
ProcessRunnable
, включая множественные вызовы ввода-вывода — как вы думаете, будут ли различия в производительности при разделении каждого шага и использовании CompletableFuture для всех операций ввода-вывода по сравнению с отправкой списка Runnables для обработки / выполнения службой-исполнителем?3. Также — я знаю, как использовать поисковую систему! Не хочу показаться легкомысленным или ленивым, просто спрашиваю, нашли ли вы сами какие-либо источники, которые, по вашему мнению, объясняют или показывают вещи особенно полезным образом. Кажется, у вас есть основания для того, чтобы задать вопрос!