ExecutorService vs Случайная Тема Спайдер

У меня есть основной вопрос о том, как 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();
    }
}

Ответ 1

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

Винс Эмиг: ExecutorService также поддерживает cachedThreadPool, который не имеет макс. Основная причина, по которой люди выбирают использование ExecutorService заключается в предотвращении накладных расходов на создание нескольких потоков (с помощью рабочих потоков). В основном это используется в тех случаях, когда многие небольшие задачи должны выполняться в отдельном потоке. Также не забывайте про singleThreadExecutor.

Теперь по теме Runnable vs Callable легко увидеть из ваших примеров. Callable может вернуть заполнитель значения (Future), который в конечном итоге будет заполнен фактическим значением в будущем. Runnable не может ничего вернуть.

Винс Эмиг: Runnable также не может генерировать исключения, в то время как Callable может.

Ответ 2

ExecutorService предоставляет множество преимуществ по сравнению с обычными нитями

  • Вы можете создавать/управлять/контролировать жизненный цикл потоков и оптимизировать накладные расходы на создание потоков
  • Вы можете контролировать обработку заданий ( "Работа кражи", "ForkJoinPool", "invokeAll" ) и т.д.
  • Вы можете планировать задачи в будущем
  • Вы можете отслеживать ход и здоровье потоков.

Даже для одного потока я предпочитаю использовать Executors.newFixedThreadPool(1);

Посмотрите на связанные вопросы SE:

Java-приложение Fork/Join vs ExecutorService - когда использовать, который?

В чем преимущества использования ExecutorService?