Elija entre la ejecución de ExecutorService y la ejecución de ExecutorService


194

¿Cómo debo elegir entre el envío o la ejecución del ExecutorService , si el valor devuelto no es de mi incumbencia?

Si pruebo ambos, no vi ninguna diferencia entre los dos excepto el valor devuelto.

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

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

Respuestas:


204

Hay una diferencia con respecto al manejo de excepciones / errores.

Una tarea en cola execute()que genere algo Throwablehará que se invoque UncaughtExceptionHandlerla Threadejecución de la tarea. El valor predeterminado UncaughtExceptionHandler, que normalmente imprime el Throwableseguimiento de la pila System.err, se invocará si no se ha instalado un controlador personalizado.

Por otro lado, un Throwablegenerado por una tarea en cola con submit()vinculará Throwableal Futureque se produjo desde la llamada a submit(). Llamar get()a eso Futurearrojará un ExecutionExceptioncon el original Throwablecomo su causa (accesible llamando getCause()al ExecutionException).


19
Tenga en cuenta que este comportamiento no está garantizado, ya que depende de si su Runnableenvoltura se realiza Tasko no, sobre lo cual puede no tener control. Por ejemplo, si su Executores realmente un ScheduledExecutorService, su tarea se envolverá internamente en una Futurey los Throwables no capturados se vincularán a este objeto.
rxg

44
Me refiero a 'envuelto en un Futureo no', por supuesto. Consulte el Javadoc para ScheduledThreadPoolExecutor # execute , por ejemplo.
rxg

60

ejecutar : Úselo para disparar y olvidar llamadas

enviar : Úselo para inspeccionar el resultado de la llamada al método y tomar las medidas apropiadas en caso deFutureobjeción devuelta por la llamada

De javadocs

submit(Callable<T> task)

Envía una tarea de devolución de valor para su ejecución y devuelve un futuro que representa los resultados pendientes de la tarea.

Future<?> submit(Runnable task)

Envía una tarea Ejecutable para su ejecución y devuelve un Futuro que representa esa tarea.

void execute(Runnable command)

Ejecuta el comando dado en algún momento en el futuro. El comando puede ejecutarse en un nuevo hilo, en un hilo agrupado, o en el hilo de llamada, a discreción de la implementación del Ejecutor.

Debe tomar precauciones mientras lo usa submit(). Oculta la excepción en el propio marco a menos que inserte su código de tarea en try{} catch{}bloque.

Código de ejemplo: este código se traga 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();
    }
}

salida:

java ExecuteSubmitDemo
creating service
a and b=4:0

El mismo código arroja reemplazando submit()con execute():

Reemplazar

service.submit(new Runnable(){

con

service.execute(new Runnable(){

salida:

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)

¿Cómo manejar este tipo de escenarios mientras se usa submit ()?

  1. Incruste su código de tarea ( implementación ejecutable o invocable) con el código de bloque try {} catch {}
  2. Implementar CustomThreadPoolExecutor

Nueva solución:

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

salida:

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

Buena explicación crujiente. Aunque extenderlo NO es realmente necesario. Solo ese futuro objeto tiene que ser consumido para saber si la tarea fue exitosa o no. por lo tanto, use submit () si planea consumir Future <t> de lo contrario simplemente use execute ()
prash

11

si no le importa el tipo de retorno, use ejecutar. es lo mismo que enviar, solo sin el regreso de Future.


15
Esto no es correcto según la respuesta aceptada. El manejo de excepciones es una diferencia bastante significativa.
Zero3

7

Tomado del Javadoc:

El método submitextiende el método base {@link Executor # execute} al crear y devolver un {@link Future} que se puede usar para cancelar la ejecución y / o esperar a que se complete.

Personalmente prefiero el uso de execute porque se siente más declarativo, aunque esto realmente es una cuestión de preferencia personal.

Para dar más información: en el caso de la ExecutorServiceimplementación, la implementación principal que devuelve la llamada a Executors.newSingleThreadedExecutor()es a ThreadPoolExecutor.

Las submitllamadas son proporcionadas por su padre AbstractExecutorServicey todas las llamadas se ejecutan internamente. ejecutar es anulado / proporcionado por el ThreadPoolExecutordirectamente.


2

Del Javadoc :

El comando puede ejecutarse en un nuevo hilo, en un hilo agrupado, o en el hilo de llamada, a discreción de la implementación del Ejecutor.

Por lo tanto, dependiendo de la implementación, Executores posible que el hilo de envío se bloquee mientras se ejecuta la tarea.


1

La respuesta completa es una composición de dos respuestas que se publicaron aquí (más un poco "extra"):

  • Al enviar una tarea (en lugar de ejecutarla), obtiene un futuro que puede utilizarse para obtener el resultado o cancelar la acción. No tienes este tipo de control cuando execute(debido a su id de tipo de retorno void)
  • executeespera que un Runnablemomento submitpueda tomar a Runnableo a Callablecomo argumento (para obtener más información sobre la diferencia entre los dos, consulte a continuación).
  • executedispara cualquier excepción no verificada de inmediato (¡no puede arrojar excepciones marcadas!), mientras que submitvincula cualquier tipo de excepción al futuro que regrese como resultado, y solo cuando llame a future.get()una excepción (envuelta) se lanzará. El Throwable que obtendrás es una instancia ExecutionExceptiony, si llamas a este objeto, getCause()devolverá el Throwable original.

Algunos puntos más (relacionados):

  • Incluso si la tarea que desea submitno requiere devolver un resultado, aún puede usar Callable<Void>(en lugar de usar a Runnable).
  • La cancelación de tareas se puede hacer utilizando el mecanismo de interrupción . Aquí hay un ejemplo de cómo implementar una política de cancelación

En resumen, es una mejor práctica usar submitcon un Callable(vs.con executeun Runnable). Y citaré de "concurrencia de Java en la práctica" Por Brian Goetz:

6.3.2 Tareas con resultados: invocables y futuros

El marco Ejecutor utiliza Runnable como su representación de tarea básica. Runnable es una abstracción bastante limitante; ejecutar no puede devolver un valor o lanzar excepciones marcadas, aunque puede tener efectos secundarios como escribir en un archivo de registro o colocar un resultado en una estructura de datos compartida. Muchas tareas son cálculos diferidos de manera efectiva: ejecutar una consulta de base de datos, buscar un recurso a través de la red o calcular una función complicada. Para este tipo de tareas, Callable es una mejor abstracción: espera que el punto de entrada principal, call, devuelva un valor y anticipa que podría arrojar una excepción.7 Los ejecutores incluyen varios métodos de utilidad para ajustar otros tipos de tareas, incluido Runnable y java.security.PrivilegedAction, con un invocable.


1

Solo agregando a la respuesta aceptada

Sin embargo, las excepciones generadas por las tareas llegan al controlador de excepciones no capturadas solo para las tareas enviadas con execute (); para las tareas enviadas con submit () al servicio ejecutor, cualquier excepción lanzada se considera parte del estado de retorno de la tarea.

Fuente

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.