Por que usar o ExecutorService para threads de longa duração?

8

Quero um objeto que gerará um encadeamento daemon que continuará sendo executado durante o tempo de vida do processo. Digamos, apenas por uma questão de argumento, que é um encadeamento em um sistema incorporado e aguarda para receber e manipular comandos em alguma porta de diagnóstico. Mas, poderia ser qualquer coisa realmente. A idéia principal é que ele esteja assistindo algo por um longo período de tempo; Não está executando uma sequência de tarefas .

A sabedoria comum sobre Java diz: Nunca instancia Thread, use um ExecutorService. (Por exemplo, veja esta resposta ) Mas qual é o benefício? Usar um conjunto de encadeamentos como um meio para criar um único encadeamento de longa execução parece inútil. Como seria melhor do que se eu escrevesse isso?

class Foobar {
    public Foobar() {
        this.threadFactory = Executors.defaultThreadFactory();
        ...
    }

    public Foobar(ThreadFactory threadFactory) {
        this.threadFactory = threadFactory;
        ...
    }

    public void start() {
        fooThread = threadFactory.newThread(new Runnable() { ... });
        fooThread.setDaemon(true);
        fooThread.start();
    }
    ...
}

Nota: esta pergunta parece semelhante à minha, mas as respostas dizem apenas como usar um pool de threads, não o porquê .

Salomão Lento
fonte
Não tenho certeza sobre Java, mas outras linguagens geralmente recomendam explicitamente evitar um conjunto de encadeamentos para qualquer encadeamento de execução longa. Como você disse, não faz sentido usar um de um número limitado de recursos por um longo período de tempo.
Frank Hileman

Respostas:

11

Eu acho que "Nunca" é uma palavra muito forte. É mais como a segunda regra de otimização: "Não (ainda)".

Na minha opinião, o principal motivo para usar um serviço executor é gerenciar o número de threads em execução. Se você permitir que classes arbitrárias criem seus próprios encadeamentos, poderá se encontrar rapidamente com 1.000 encadeamentos vinculados à CPU (ou com alternância constante de contexto). Um serviço executor resolve esse problema, fornecendo restrições na criação do encadeamento.

Um motivo secundário é que o início adequado de um encadeamento requer alguma reflexão:

  • Daemon ou não daemon? Cometa este erro e você precisa ligar System.exit()para desligar o programa.
  • Que prioridade? Muitos programadores do Swing achavam que estavam sendo inteligentes ativando os threads de segundo plano para lidar com tarefas intensivas da CPU, mas não perceberam que o thread de despacho de eventos é executado com a prioridade mais alta e os threads filhos herdam a prioridade dos pais.
  • Como lidar com exceções não capturadas?
  • O que é um nome apropriado? Parece bobo ficar obcecado por um nome, mas os threads nomeados para fins ajudam bastante na depuração.

Dito isto, certamente há casos em que é apropriado ativar threads em vez de confiar em um pool de threads.

  • Eu usaria o pool de threads no caso em que meu aplicativo está gerando tarefas e desejo controlar como essas tarefas são executadas.
  • Eu criaria threads dedicados nos quais um ou alguns threads são dedicados ao processamento de eventos gerados externamente .

"Gerado externamente" depende do contexto. O caso clássico é o encadeamento no final de uma fila ou soquete de mensagens, que precisa pesquisar essa fila e executar alguma ação (que pode envolver o envio de uma tarefa para um pool).

No entanto, também pode se referir a eventos gerados fora de um módulo específico: por exemplo, considero perfeitamente razoável que o Log4J gire AsyncAppenderseu próprio encadeamento em vez de esperar que o aplicativo forneça um pool.

kdgregory
fonte