Escolha entre o envio do ExecutorService e o executor do ExecutorService


194

Como devo escolher entre o envio ou execução do ExecutorService , se o valor retornado não for da minha conta?

Se eu testar os dois, não vi diferenças entre os dois, exceto o valor retornado.

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

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

Respostas:


204

Há uma diferença em relação à manipulação de exceção / erro.

Uma tarefa enfileirada execute()que gera alguns Throwablefará com UncaughtExceptionHandlerque a Threadexecução da tarefa seja invocada. O padrão UncaughtExceptionHandler, que normalmente imprime o Throwablerastreamento de pilha System.err, será chamado se nenhum manipulador personalizado tiver sido instalado.

Por outro lado, um Throwablegerado por uma tarefa na fila submit()vinculará Throwableo Futureque foi produzido a partir da chamada para submit(). A chamada get()que Futureacionará um ExecutionExceptioncom o original Throwablecomo causa (acessível acessando getCause()o ExecutionException).


19
Observe que esse comportamento não é garantido, pois depende de o seu Runnableenvolvimento Taskou não, que você pode não ter controle. Por exemplo, se você Executoré realmente um ScheduledExecutorService, sua tarefa será envolvida internamente em um Futuree Throwables não capturados serão vinculados a esse objeto.
rxg

4
Quero dizer "embrulhado em um Futureou não", é claro. Consulte o Javadoc para ScheduledThreadPoolExecutor # execute , por exemplo.
rxg

60

execute : use-o para disparar e esquecer chamadas

submit : use-o para inspecionar o resultado da chamada do método e tomar as ações apropriadas contra asFutureobjeções retornadas pela chamada

De javadocs

submit(Callable<T> task)

Envia uma tarefa de retorno de valor para execução e retorna um Futuro representando os resultados pendentes da tarefa.

Future<?> submit(Runnable task)

Envia uma tarefa executável para execução e retorna um futuro representando essa tarefa.

void execute(Runnable command)

Executa o comando fornecido em algum momento no futuro. O comando pode ser executado em um novo thread, em um thread em pool ou no thread de chamada, a critério da implementação do Executor.

Você deve tomar precauções ao usar submit(). Ele oculta exceção na própria estrutura, a menos que você incorpore seu código de tarefa em try{} catch{}bloco.

Código de exemplo: esse código é engolido 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();
    }
}

resultado:

java ExecuteSubmitDemo
creating service
a and b=4:0

O mesmo código é lançado substituindo submit()por execute():

Substituir

service.submit(new Runnable(){

com

service.execute(new Runnable(){

resultado:

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)

Como lidar com esse tipo de cenário ao usar submit ()?

  1. Incorpore seu código de tarefa ( implementação Runnable ou Callable) com o código de bloco try {} catch {}
  2. Implemento CustomThreadPoolExecutor

Nova solução:

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

resultado:

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

Boa explicação nítida. Embora a extensão NÃO seja realmente necessária. Apenas esse objeto futuro precisa ser consumido para saber se a tarefa foi bem-sucedida ou não. Assim, o uso submit () se você está planejando para consumir Futuro <t> caso contrário basta usar execute ()
prash

11

Se você não se importa com o tipo de retorno, use execute. é o mesmo que enviar, apenas sem o retorno do futuro.


15
Isso não está correto de acordo com a resposta aceita. O tratamento de exceções é uma diferença bastante significativa.
Zero3 31/05

7

Retirado do Javadoc:

O método submitestende o método base {@link Executor # execute} criando e retornando um {@link Future} que pode ser usado para cancelar a execução e / ou aguardar a conclusão.

Pessoalmente, prefiro o uso de execute porque parece mais declarativo, embora isso realmente seja uma questão de preferência pessoal.

Para fornecer mais informações: no caso da ExecutorServiceimplementação, a implementação principal retornada pela chamada para Executors.newSingleThreadedExecutor()é umThreadPoolExecutor .

As submitchamadas são fornecidas pelo pai AbstractExecutorServicee todas as chamadas são executadas internamente. execute é substituído / fornecido pelo ThreadPoolExecutordiretamente.


2

Do Javadoc :

O comando pode ser executado em um novo thread, em um thread em pool ou no thread de chamada, a critério da implementação do Executor.

Portanto, dependendo da implementação, Executorvocê pode descobrir que o encadeamento de envio bloqueia enquanto a tarefa está em execução.


1

A resposta completa é uma composição de duas respostas que foram publicadas aqui (mais um pouco "extra"):

  • Ao enviar uma tarefa (versus executá-la), você recebe um futuro que pode ser usado para obter o resultado ou cancelar a ação. Você não tem esse tipo de controle quando você execute(porque seu ID de tipo de retorno void)
  • executeespera que um Runnabletempo submitpossa levar a Runnableou a Callablecomo argumento (para obter mais informações sobre a diferença entre os dois - veja abaixo).
  • executeelimina imediatamente quaisquer exceções não verificadas (não pode lançar exceções verificadas !!!), enquanto submitvincula qualquer tipo de exceção ao futuro que retornar como resultado e somente quando você chama future.get()uma exceção (empacotada) será lançada. O Throwable que você receberá é uma instância ExecutionExceptione, se você chamar esse objeto, getCause()ele retornará o Throwable original.

Mais alguns pontos (relacionados):

  • Mesmo que a tarefa que você deseja submitnão exija retorno de um resultado, você ainda pode usar Callable<Void>(em vez de usar umRunnable ).
  • O cancelamento de tarefas pode ser feito usando o mecanismo de interrupção . Aqui está um exemplo de como implementar uma política de cancelamento

Para resumir, é uma prática melhor usar submitcom a Callable(vs. executecom a Runnable). E citarei "concorrência simultânea de Java na prática", por Brian Goetz:

6.3.2 Tarefas geradoras de resultados: exigíveis e futuras

A estrutura do Executor usa Runnable como sua representação básica de tarefas. Executável é uma abstração bastante limitadora; A execução não pode retornar um valor ou lançar exceções verificadas, embora possa ter efeitos colaterais, como gravar em um arquivo de log ou colocar um resultado em uma estrutura de dados compartilhada. Muitas tarefas são efetivamente adiadas - executando uma consulta ao banco de dados, buscando um recurso na rede ou computando uma função complicada. Para esses tipos de tarefas, Callable é uma abstração melhor: espera que o ponto de entrada principal, call, retorne um valor e antecipa que possa gerar uma exceção.7 Os executores incluem vários métodos utilitários para agrupar outros tipos de tarefas, incluindo Runnable e java.security.PrivilegedAction, com um Callable.


1

Apenas adicionando à resposta aceita-

No entanto, exceções lançadas de tarefas o tornam para o manipulador de exceções não capturadas apenas para tarefas enviadas com execute (); para tarefas enviadas com submit () ao serviço do executor, qualquer exceção lançada é considerada parte do status de retorno da tarefa.

Fonte

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.