Explicação detalhada da classe de ferramentas Executors em Java

Executores

Executors é uma classe utilitária em Java. Fornece métodos de fábrica para criar diferentes tipos de conjuntos de encadeamentos.

![][2]

Também pode ser visto na figura acima que o método Executors de criar um pool de threads e o pool de threads criado implementam a interface ExecutorService. Os métodos comumente usados ​​são os seguintes:

newFiexedThreadPool(int Threads): crie um pool de encadeamentos com um número fixo de encadeamentos.

newCachedThreadPool(): Cria um pool de encadeamentos que pode ser armazenado em cache, chamando execute para reutilizar os encadeamentos construídos anteriormente (se os encadeamentos estiverem disponíveis). Se nenhum encadeamento estiver disponível, um novo encadeamento será criado e adicionado ao pool. Encerra e remove threads que não foram usados ​​por 60 segundos do cache.

newSingleThreadExecutor()Crie um Executor de thread único.

newScheduledThreadPool(int corePoolSize)Crie um pool de encadeamentos que suporte tempo e execução de tarefa periódica, que pode ser usado para substituir a classe Timer na maioria dos casos.

A classe parece ser relativamente poderosa, e usa o modelo de fábrica e tem escalabilidade relativamente forte. O importante é que seja mais conveniente de usar, como:

ExecutorService executor = Executors.newFixedThreadPool(nThreads) ;

Um pool de threads de tamanho fixo pode ser criado.

Mas por que eu digo que não é recomendado que você use esta classe para criar um pool de threads?

O que mencionei é "não recomendado", mas também está claramente declarado no Alibaba Java Development Manual, e a palavra usada é "não é permitido" usar Executores para criar pools de threads.

O que há de errado com os executores

É mencionado no Alibaba Java Development Manual que o uso de Executors para criar um pool de threads pode causar OOM (OutOfMemory, estouro de memória), mas não explica o porquê, então vamos dar uma olhada em por que os Executors não podem ser usados?

Vamos começar com um exemplo simples para simular a situação em que o uso de Executors leva a OOM.

/**
 * @author Hollis
 */
public class ExecutorsDemo {
    private static ExecutorService executor = Executors.newFixedThreadPool(15);
    public static void main(String[] args) {
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            executor.execute(new SubThread());
        }
    }
}


class SubThread implements Runnable {
    @Override
    public void run() {
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            //do nothing
        }
    }
}

Ao especificar os parâmetros da JVM: -Xmx8m -Xms8ma execução do código acima gerará OOM:

Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
    at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)
    at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)
    at com.hollis.ExecutorsDemo.main(ExecutorsDemo.java:16)

O código acima indica ExecutorsDemo.javaque a linha 16 de está no código executor.execute(new SubThread());.

Por que os executores são falhos

Através do exemplo acima, sabemos que Executorso pool de threads criado corre o risco de OOM, então qual é o motivo? Precisamos de um Executorscódigo-fonte detalhado para analisá-lo.

De fato, na mensagem de erro acima, podemos ver as pistas.No código acima, foi dito que a verdadeira causa do OOM é, na verdade, o LinkedBlockingQueue.offermétodo.

Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
    at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)
    at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)
    at com.hollis.ExecutorsDemo.main(ExecutorsDemo.java:16)

Se os leitores olharem para o código, eles também podem descobrir que a camada inferior é de fato LinkedBlockingQueueimplementada por meio de:

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());

Se o leitor souber algo sobre o bloqueio de filas em Java, você poderá entender o motivo aqui.

Existem principalmente duas implementações em Java BlockingQueue, ou seja, ArrayBlockingQueuee LinkedBlockingQueue.

ArrayBlockingQueueÉ uma fila de bloqueio limitada implementada com uma matriz e a capacidade deve ser definida.

LinkedBlockingQueueÉ uma fila de bloqueio limitada implementada com uma lista vinculada. A capacidade pode ser definida opcionalmente. Se não for definida, será uma fila de bloqueio ilimitada com comprimento máximo de Integer.MAX_VALUE.

O problema aqui é: Se ** não for definido, será uma fila de bloqueio ilimitada com comprimento máximo de Integer.MAX_VALUE. ** Ou seja, se não definirmos LinkedBlockingQueuea capacidade, sua capacidade padrão será Integer.MAX_VALUE.

Quando newFixedThreadPoolcriado LinkedBlockingQueue, nenhuma capacidade é especificada. No momento, LinkedBlockingQueueé uma fila ilimitada. Para uma fila ilimitada, as tarefas podem ser adicionadas continuamente à fila. Nesse caso, pode haver problemas de estouro de memória devido a muitas tarefas.

Os problemas mencionados acima são refletidos principalmente nos newFixedThreadPooldois newSingleThreadExecutormétodos de fábrica. Isso não significa newCachedThreadPoolque newScheduledThreadPoolesses dois métodos sejam seguros. O número máximo de threads criados por esses dois métodos pode ser Integer.MAX_VALUE, e criar tantos threads inevitavelmente levará ao OOM.

A postura correta para criar um pool de threads

Evite usar Executors para criar um pool de threads, principalmente para evitar o uso da implementação padrão, então podemos chamar diretamente o ThreadPoolExecutorconstrutor para criarmos nós mesmos o pool de threads. No momento da criação, BlockQueuebasta especificar a capacidade.

private static ExecutorService executor = new ThreadPoolExecutor(10, 10,
        60L, TimeUnit.SECONDS,
        new ArrayBlockingQueue(10));

Nesse caso, uma vez que o número de threads enviados exceder o número atual de threads disponíveis, ele será lançado java.util.concurrent.RejectedExecutionException. Isso ocorre porque a fila usada pelo pool de threads atual é uma fila limitada e a fila não pode continuar processando novas solicitações quando está cheio. Mas exceções (Exception) são melhores que erros (Error).

Além de sua própria definição ThreadPoolExecutor. Existem outras maneiras. Neste momento, a primeira vez que você deve pensar em bibliotecas de código aberto, como apache e goiaba.

O autor recomenda usar o ThreadFactoryBuilder fornecido pela goiaba para criar um pool de threads.

public class ExecutorsDemo {


    private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
        .setNameFormat("demo-pool-%d").build();
    
    private static ExecutorService pool = new ThreadPoolExecutor(5, 200,
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
    
    public static void main(String[] args) {
    
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            pool.execute(new SubThread());
        }
    }

}

Ao criar um encadeamento da maneira acima, não apenas o problema OOM pode ser evitado, mas o nome do encadeamento também pode ser personalizado, facilitando o rastreamento da origem dos erros.

Pensando na questão, o autor do artigo disse: É melhor ter uma exceção (Exception) do que um erro (Error), por que você diz isso?

Acho que você gosta

Origin blog.csdn.net/zy_dreamer/article/details/132350963
Recomendado
Clasificación