Wybierz pomiędzy przesłaniem ExecutorService a executorem ExecutorService

194

Jak wybrać pomiędzy przesłaniem lub wykonaniem przez ExecutorService , jeśli zwracana wartość nie jest moim przedmiotem?

Jeśli przetestuję oba, nie zauważyłem żadnych różnic między nimi oprócz zwróconej wartości.

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

ExecutorService threadExecutor = Executors.newSingleThreadExecutor();
threadExecutor.submit(new Task());
Cheok Yan Cheng
źródło

Odpowiedzi:

204

Różnica dotyczy obsługi wyjątków / błędów.

Zadanie w kolejce, execute()które generuje niektóre Throwable, spowoduje, UncaughtExceptionHandlerże Threaduruchomione zadanie zostanie wywołane. Domyślne UncaughtExceptionHandler, które zwykle drukuje Throwableślad stosu System.err, zostanie wywołane, jeśli nie zostanie zainstalowany niestandardowy moduł obsługi.

Z drugiej strony, Throwablewygenerowane przez zadanie w kolejce submit()spowoduje powiązanie z Throwabletym, Futurektóry został wygenerowany z wywołania do submit(). Dzwoniąc get()na który Futurewygeneruje ExecutionExceptionz oryginałem Throwablejako jej przyczynę (dostępnym poprzez wywołanie getCause()na ExecutionException).

hochraldo
źródło
19
Zauważ, że takie zachowanie nie jest gwarantowane, ponieważ zależy od tego, czy twoje Runnableotoczenie jest owinięte, Taskczy też nie, nad którym możesz nie mieć kontroli. Na przykład, jeśli twoje Executorjest w rzeczywistości ScheduledExecutorService, twoje zadanie zostanie wewnętrznie owinięte, Futurea nieprzechwycone Throwables zostaną powiązane z tym obiektem.
rxg
4
FutureOczywiście mam na myśli „zawinięty w lub nie”. Zobacz na przykład Javadoc dla ScheduledThreadPoolExecutor # execute .
rxg
60

wykonaj : Użyj go do strzelania i zapominania połączeń

Prześlij : użyj go, aby sprawdzić wynik wywołania metody i podjąć odpowiednie działania w przypadkuFuturesprzeciwu zwróconego przez wywołanie

Z javadocs

submit(Callable<T> task)

Przekazuje zadanie zwracające wartość do wykonania i zwraca Przyszłość reprezentującą oczekujące wyniki zadania.

Future<?> submit(Runnable task)

Przekazuje zadanie Runnable do wykonania i zwraca Future reprezentującą to zadanie.

void execute(Runnable command)

Wykonuje podaną komendę w przyszłości. Polecenie może zostać wykonane w nowym wątku, w puli wątku lub w wątku wywołującym, według uznania implementacji Executora.

Podczas używania należy zachować ostrożność submit(). Ukrywa wyjątek w samej strukturze, chyba że kod zadania zostanie osadzony w try{} catch{}bloku.

Przykładowy kod: ten kod połyka 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();
    }
}

wynik:

java ExecuteSubmitDemo
creating service
a and b=4:0

Ten sam kod rzuca zastępując submit()z execute():

Zastąpić

service.submit(new Runnable(){

z

service.execute(new Runnable(){

wynik:

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)

Jak obsłużyć tego typu scenariusze podczas korzystania z funkcji Submit ()?

  1. Osadź swój kod zadania ( implementację Runnable lub Callable) za pomocą kodu blokowego {} catch {}
  2. Wprowadzić w życie CustomThreadPoolExecutor

Nowe rozwiązanie:

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);
   }
 }

wynik:

java ExecuteSubmitDemo
creating service
a and b=4:0
java.lang.ArithmeticException: / by zero
Ravindra babu
źródło
Dobre wyjaśnienie. Chociaż jego rozszerzenie NIE jest tak naprawdę wymagane. Tylko ten przyszły obiekt musi zostać zużyty, aby wiedzieć, czy zadanie zakończyło się sukcesem, czy nie. dlatego użyj subm (), jeśli planujesz wykorzystać Future <t> w przeciwnym razie po prostu użyj execute ()
prash
11

jeśli nie zależy ci na typie zwrotu, użyj execute. to to samo, co prześlij, tylko bez powrotu Future.

Steven
źródło
15
To nie jest poprawne zgodnie z przyjętą odpowiedzią. Obsługa wyjątków jest dość znaczącą różnicą.
Zero3
7

Zaczerpnięte z Javadoc:

Metoda submitrozszerza podstawową metodę {@link Executor # execute}, tworząc i zwracając {@link Future}, której można użyć do anulowania wykonania i / lub oczekiwania na zakończenie.

Osobiście wolę użycie execute, ponieważ wydaje się bardziej deklaratywne, chociaż tak naprawdę jest to kwestia osobistych preferencji.

Aby podać więcej informacji: w przypadku ExecutorServiceimplementacji podstawową implementacją zwracaną przez wywołanie Executors.newSingleThreadedExecutor()jestThreadPoolExecutor .

Te submitpołączenia są dostarczane przez jego rodzica AbstractExecutorServicei wszystkie połączenia wykonać wewnętrznie. Wykonaj jest nadpisane / dostarczone przez ThreadPoolExecutorbezpośrednio.

Składnia
źródło
2

Z Javadoc :

Polecenie może zostać wykonane w nowym wątku, w puli wątku lub w wątku wywołującym, według uznania implementacji Executora.

Więc w zależności od wdrożenia Executor może się okazać, że wątek wysyłający blokuje się podczas wykonywania zadania.

rxg
źródło
1

Pełna odpowiedź jest kompozycją dwóch odpowiedzi, które zostały tutaj opublikowane (plus trochę „dodatkowych”):

  • Przesyłając zadanie (a nie wykonując je), odzyskujesz przyszłość, której można użyć do uzyskania wyniku lub anulowania działania. Nie masz takiej kontroli, gdy execute(ponieważ jest to identyfikator typu zwracanego void)
  • executeoczekuje, że jakiś Runnableczas submitmoże potrwać jako argument a Runnablelub a Callable(więcej informacji na temat różnicy między nimi - patrz poniżej).
  • executewywołuje wszystkie odznaczone wyjątki od razu (nie może generować sprawdzonych wyjątków !!!), jednocześnie submitwiążąc każdy rodzaj wyjątku z przyszłością, która powróci w wyniku, i tylko wtedy, gdy wywołasz future.get()(zawinięty) wyjątek zostanie zgłoszony. Thralble, który dostaniesz, jest instancją, ExecutionExceptiona jeśli nazwiesz ten obiekt, getCause()zwróci oryginalny Throwable.

Kilka innych (powiązanych) punktów:

  • Nawet jeśli zadanie, które chcesz wykonać submit, nie wymaga zwrócenia wyniku, nadal możesz go użyć Callable<Void>(zamiast używaćRunnable ).
  • Anulowanie zadań można wykonać za pomocą mechanizmu przerwania . Oto przykład, jak wdrożyć zasady anulowania

Podsumowując, lepszą praktyką jest używanie submitz Callable(w porównaniu executez a Runnable). Cytuję z „Współbieżności Java w praktyce” Briana Goetza:

6.3.2 Zadania przynoszące wyniki: możliwe do wywołania i przyszłości

Środowisko Executor używa Runnable jako podstawowej reprezentacji zadania. Runnable jest dość ograniczającą abstrakcją; Uruchom nie może zwrócić wyjątków lub sprawdzonych wartości, chociaż może mieć skutki uboczne, takie jak zapis do pliku dziennika lub umieszczenie wyniku we wspólnej strukturze danych. Wiele zadań to efektywnie odroczone obliczenia - wykonanie zapytania do bazy danych, pobranie zasobu przez sieć lub obliczenie skomplikowanej funkcji. Dla tego typu zadań Callable jest lepszą abstrakcją: oczekuje, że główny punkt wejścia, call, zwróci wartość i przewiduje, że może wygenerować wyjątek. i java.security.PrivilegedAction, z funkcją wywoływania.

alfasin
źródło
1

Po prostu dodając do zaakceptowanej odpowiedzi

Jednak wyjątki zgłaszane przez zadania trafiają do nieprzechwyconego modułu obsługi wyjątków tylko dla zadań przesłanych za pomocą execute (); w przypadku zadań przesłanych z funkcją replace () do usługi modułu wykonującego każdy zgłoszony wyjątek jest uważany za część statusu zwracanego zadania.

Źródło

abhihello123
źródło