ExecutorService vs Casual Spawner
У меня есть основной вопрос о том, как ExecutorService
работает на Java.
Довольно сложно увидеть разницу между простым Threads
выполнять некоторые задачи параллельно и назначать каждую задачу ThreadPool
,
ExecutorService
также выглядит очень простым и эффективным в использовании, поэтому мне было интересно, почему мы не используем его все время.
Это просто вопрос, как выполнять свою работу быстрее, чем другой?
Вот два очень простых примера, чтобы показать разницу между этими двумя способами:
Использование службы исполнителя: Hello World (задача)
static class HelloTask implements Runnable {
String msg;
public HelloTask(String msg) {
this.msg = msg;
}
public void run() {
long id = Thread.currentThread().getId();
System.out.println(msg + " from thread:" + id);
}
}
Использование сервиса executor: Hello World (создание исполнителя, отправка)
static class HelloTask {
public static void main(String[] args) {
int ntasks = 1000;
ExecutorService exs = Executors.newFixedThreadPool(4);
for (int i=0; i<ntasks; i++) {
HelloTask t = new HelloTask("Hello from task " + i);
exs.submit(t);
}
exs.shutdown();
}
}
ниже показан аналогичный пример, но, расширяя интерфейс Callable, не могли бы вы сказать мне разницу между этими двумя и в каких случаях следует использовать определенный вместо другого?
Использование службы исполнителя: Счетчик (задача)
static class HelloTaskRet implements Callable<Long> {
String msg;
public HelloTaskRet(String msg) {
this.msg = msg; }
public Long call() {
long tid = Thread.currentThread().getId();
System.out.println(msg + " from thread:" + tid);
return tid;
}
}
Использование службы исполнителя: (создание, отправка)
static class HelloTaskRet {
public static void main(String[] args) {
int ntasks = 1000;
ExecutorService exs = Executors.newFixedThreadPool(4);
Future<Long>[] futures = (Future<Long>[]) new Future[ntasks];
for (int i=0; i<ntasks; i++) {
HelloTaskRet t = new HelloTaskRet("Hello from task " + i);
futures[i] = exs.submit(t);
}
exs.shutdown();
}
}
2 ответа
Хотя вопрос и пример кода не коррелируют, я постараюсь уточнить оба. Преимущество ExecutorService перед случайно порождаемыми потоками состоит в том, что он ведет себя предсказуемо и избегает накладных расходов на создание потоков, что является относительно большим для JVM (например, ему необходимо зарезервировать память для каждого потока). По предсказуемости, по крайней мере, для fixedThreadPool
Я имею в виду, что вы знаете максимальное количество одновременных потоков и знаете, когда и как они могут быть созданы (чтобы ваша JVM не взорвалась в случае внезапных пиков).
Винс Эмиг:
ExecutorService
также поддерживаетcachedThreadPool
, который не имеет макс. Основная причина, по которой люди выбирают использоватьExecutorService
заключается в предотвращении накладных расходов на создание нескольких потоков (с помощью рабочих потоков). В основном это используется в тех случаях, когда многие небольшие задачи должны выполняться в отдельном потоке. Также не забывайте оsingleThreadExecutor
,
Теперь по теме Runnable
против Callable
Это легко увидеть из ваших примеров. Callable
s может вернуть значение заполнитель (Future
) который в конечном итоге будет заполнен фактическим значением в будущем. Runnable
s не может ничего вернуть.
Винс Эмиг:
Runnable
также не может генерировать исключения, в то время какCallable
Можно.
ExecutorService предоставляет много преимуществ по сравнению с простыми потоками
- Вы можете создавать / управлять / контролировать жизненный цикл потоков и оптимизировать накладные расходы на создание потоков
- Вы можете контролировать обработку заданий ( Work Stealing, ForkJoinPool, invokeAll) и т. Д.
- Вы можете планировать задачи в будущем времени
- Вы можете отслеживать прогресс и здоровье потоков
Даже для одной темы я предпочитаю использовать Executors.newFixedThreadPool(1);
Посмотрите на связанные вопросы SE:
Java Fork/Join vs ExecutorService - когда использовать какой?