Виберіть між поданням ExecutorService та виконанням ExecutorService


194

Як мені вибрати між поданням або виконанням програми ExecutorService , якщо повернене значення не викликає занепокоєння?

Якщо я тестую обидва, я не бачив різниці між ними, окрім повернутого значення.

ExecutorService threadExecutor = Executors.newSingleThreadExecutor();
threadExecutor.execute(new Task());

ExecutorService threadExecutor = Executors.newSingleThreadExecutor();
threadExecutor.submit(new Task());

Відповіді:


204

Існує відмінність щодо винятку / обробки помилок.

Завдання в черзі з , execute()що породжує деякі Throwableвикличе UncaughtExceptionHandlerдля Threadвиконання завдання , яка буде викликано. За замовчуванням UncaughtExceptionHandler, який зазвичай друкує Throwableслід стека System.err, буде викликано, якщо не встановлено спеціальний обробник.

З іншого боку, Throwableзгенерована завданням, що стоять у черзі, submit()буде прив'язувати Throwableдо того, Futureщо було створено під час виклику до submit(). Якщо зателефонувати, get()це Futureпризведе ExecutionExceptionдо оригіналу Throwableяк до його причини (доступний за допомогою дзвінка getCause()на телефон ExecutionException).


19
Зауважте, що така поведінка не гарантується, оскільки це залежить від того, Runnableзаплутаєтесь Taskчи ні, чи не маєте ви над цим контролю. Наприклад, якщо ваше Executorнасправді є ScheduledExecutorService, ваше завдання буде внутрішньо загорнене в a, Futureа невловиме Throwables буде прив’язане до цього об'єкта.
rxg

4
Я маю на увазі Future, звичайно, «загорнутий у чи ні». Наприклад, див. Виконання Javadoc для ScheduledThreadPoolExecutor # .
rxg

61

виконання : Використовуйте його для вогню та забуття дзвінків

подати : Використовуйте його для огляду результату виклику методу та вжиття відповідних дій щодоFutureповернених закликомоб'єктів

Від javadocs

submit(Callable<T> task)

Подає завдання, що повертає значення для виконання, і повертає Майбутнє, що представляє очікувані результати завдання.

Future<?> submit(Runnable task)

Подає завдання Runnable для виконання та повертає майбутнє, що представляє це завдання.

void execute(Runnable command)

Виконує задану команду через деякий час у майбутньому. На розсуд виконання Виконавця команда може виконуватись у новому потоці, в об'єднаному потоці або в виклику, що викликає.

Ви повинні дотримуватися обережності під час використання submit(). Він приховує виняток у самому фреймворку, якщо ви не вставляєте код завдання в try{} catch{}блок.

Приклад коду: Цей код проковтує Arithmetic exception : / by zero.

import java.util.concurrent.*;
import java.util.*;

public class ExecuteSubmitDemo{
    public ExecuteSubmitDemo()
    {
        System.out.println("creating service");
        ExecutorService service = Executors.newFixedThreadPool(10);
        //ExtendedExecutor service = new ExtendedExecutor();
        service.submit(new Runnable(){
                 public void run(){
                    int a=4, b = 0;
                    System.out.println("a and b="+a+":"+b);
                    System.out.println("a/b:"+(a/b));
                    System.out.println("Thread Name in Runnable after divide by zero:"+Thread.currentThread().getName());
                 }
            });
        service.shutdown();
    }
    public static void main(String args[]){
        ExecuteSubmitDemo demo = new ExecuteSubmitDemo();
    }
}

вихід:

java ExecuteSubmitDemo
creating service
a and b=4:0

Цей же код кидає заміною submit()на execute():

Замініть

service.submit(new Runnable(){

з

service.execute(new Runnable(){

вихід:

java ExecuteSubmitDemo
creating service
a and b=4:0
Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zero
        at ExecuteSubmitDemo$1.run(ExecuteSubmitDemo.java:14)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
        at java.lang.Thread.run(Thread.java:744)

Як обробити такий тип сценаріїв під час використання submit ()?

  1. Вставте свій код завдань ( реалізація чи зателефонований) за допомогою блоку спробу {} catch {}
  2. Реалізація CustomThreadPoolExecutor

Нове рішення:

import java.util.concurrent.*;
import java.util.*;

public class ExecuteSubmitDemo{
    public ExecuteSubmitDemo()
    {
        System.out.println("creating service");
        //ExecutorService service = Executors.newFixedThreadPool(10);
        ExtendedExecutor service = new ExtendedExecutor();
        service.submit(new Runnable(){
                 public void run(){
                    int a=4, b = 0;
                    System.out.println("a and b="+a+":"+b);
                    System.out.println("a/b:"+(a/b));
                    System.out.println("Thread Name in Runnable after divide by zero:"+Thread.currentThread().getName());
                 }
            });
        service.shutdown();
    }
    public static void main(String args[]){
        ExecuteSubmitDemo demo = new ExecuteSubmitDemo();
    }
}

class ExtendedExecutor extends ThreadPoolExecutor {

   public ExtendedExecutor() { 
       super(1,1,60,TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(100));
   }
   // ...
   protected void afterExecute(Runnable r, Throwable t) {
     super.afterExecute(r, t);
     if (t == null && r instanceof Future<?>) {
       try {
         Object result = ((Future<?>) r).get();
       } catch (CancellationException ce) {
           t = ce;
       } catch (ExecutionException ee) {
           t = ee.getCause();
       } catch (InterruptedException ie) {
           Thread.currentThread().interrupt(); // ignore/reset
       }
     }
     if (t != null)
       System.out.println(t);
   }
 }

вихід:

java ExecuteSubmitDemo
creating service
a and b=4:0
java.lang.ArithmeticException: / by zero

Гарне чітке пояснення. Хоча продовжувати його НЕ дуже потрібно. Просто цей майбутній об’єкт повинен бути спожитий, щоб знати, чи було це завдання успішним чи ні. таким чином, використовуйте submit (), якщо ви плануєте споживати Future <t> інакше просто використовуйте Execute ()
праш

11

якщо вас не хвилює тип повернення, використовуйте Execute. це те саме, що подати, лише без повернення Майбутнього.


15
Відповідно до прийнятої відповіді це неправильно. Обробка винятків - досить суттєва різниця.
Нуль3

7

Взято з Javadoc:

Метод submitрозширює базовий метод {@link Executor # execute}, створюючи та повертаючи {@link Future}, який можна використовувати для скасування виконання та / або очікування завершення.

Особисто я віддаю перевагу використанню Execute, тому що він відчуває себе більш декларативно, хоча це справді питання особистої переваги.

Для отримання додаткової інформації: у випадку ExecutorServiceреалізації основна реалізація, яка повертається викликом до, Executors.newSingleThreadedExecutor()- це a ThreadPoolExecutor.

Ці submitвиклики надаються своїм батьком AbstractExecutorServiceі все це вимагає виконання внутрішньо. Execute переосмислюється / надається ThreadPoolExecutorбезпосередньо.


2

Від Javadoc :

На розсуд виконання Виконавця команда може виконуватись у новому потоці, в об'єднаному потоці або в виклику, що викликає.

Отже, залежно від реалізації, Executorви можете виявити, що подаючий потік блокується під час виконання завдання.


1

Повна відповідь - це композиція з двох відповідей, які були опубліковані тут (плюс трохи "додатково"):

  • Подавши завдання (порівняно з його виконанням), ви отримаєте назад майбутнє, яке можна використовувати для отримання результату або скасування дії. У вас немає такого контролю, коли ви execute(тому що його тип повернення void)
  • executeочікує, що деякий Runnableчас submitможе взяти аргумент як a, Runnableчи a Callable(докладнішу інформацію про різницю між ними - див. нижче).
  • executeмиттєво підключає будь-які неперевірені винятки (він не може кидати перевірені винятки !!!), при цьому submitпов'язує будь- який виняток із майбутнім, що повертається в результаті, і лише тоді, коли ви викликаєте future.get()(завершений) виняток, буде викинуто. Throwable, який ви отримаєте, - це екземпляр, ExecutionExceptionі якщо ви будете називати цей об'єкт, getCause()він поверне початковий Throwable.

Ще кілька (споріднених) моментів:

  • Навіть якщо завдання, яке ви хочете submit, не вимагає повернення результату, ви все одно можете використовувати Callable<Void>(замість а Runnable).
  • Скасування завдань можна зробити за допомогою механізму переривання . Ось приклад того, як реалізувати політику щодо скасування

Підводячи підсумок, краще використовувати практику submitз Callable(порівняно executeз a Runnable). І я цитую з "Паралельності Java на практиці" Брайана Геца:

6.3.2 Завдання, що несуть результат: Дзвінки та майбутнє

Рамка Executor використовує Runnable як основне представлення завдань. Runnable - досить обмежуюча абстракція; run не може повернути значення або викинути перевірені винятки, хоча це може мати побічні ефекти, такі як запис у файл журналу або розміщення результату у спільній структурі даних. Багато завдань є ефективно відкладеними обчисленнями - виконання запиту до бази даних, отримання ресурсу по мережі або обчислення складної функції. Для цих типів завдань Callable - це краща абстракція: вона очікує, що головна точка входу, виклик, поверне значення і передбачає, що вона може скинути виняток.7 Виконавці включають декілька утилітних методів для обгортання інших типів завдань, включаючи Runnable та java.security.PrivilegedAction, за допомогою дзвінка.


1

Просто додавання до прийнятої відповіді-

Однак винятки, викинуті з завдань, перетворюють його на обробку винятків, що не знайдено, лише для завдань, поданих з Execute (); для завдань, що надсилаються з submit () службою виконавця, будь-який викинутий виняток вважається частиною статусу повернення завдання.

Джерело

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.