Outro tipo de pool de threads é "em cache". Esses pools de encadeamentos são tão comumente usados quanto os fixos.
Conforme indicado pelo nome, esse tipo de pool de encadeamentos armazena encadeamentos em cache. Ele mantém os threads não utilizados ativos por um tempo limitado para reutilizar esses threads para executar novas tarefas. Esse pool de threads é melhor para quando temos uma quantidade razoável de trabalho leve.
O significado de "alguma quantia razoável" é bastante amplo, mas você deve saber que esse pool não é adequado para todas as tarefas. Por exemplo, suponha que queremos criar um milhão de tarefas. Mesmo que cada um leve uma quantidade muito pequena de tempo, ainda usaremos uma quantidade irracional de recursos e degradaremos o desempenho. Também devemos evitar esses pools quando o tempo de execução for imprevisível, por exemplo, com tarefas de E/S.
Nos bastidores, o construtor ThreadPoolExecutor é chamado com os seguintes argumentos:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
Os seguintes valores são passados para o construtor como argumentos:
Parâmetro | Valor |
---|---|
corePoolSize (quantos threads estarão prontos (iniciados) quando o serviço executor for iniciado) | 0 |
maximumPoolSize (o número máximo de threads que um serviço executor pode criar) | Inteiro.MAX_VALUE |
keepAliveTime (o tempo que um thread liberado continuará a viver antes de ser destruído se o número de threads for maior que corePoolSize ) | 60L |
unidade (unidades de tempo) | TimeUnit.SECONDS |
workQueue (implementação de uma fila) | new SynchronousQueue<Executável>() |
E podemos passar nossa própria implementação de ThreadFactory exatamente da mesma maneira.
Vamos falar sobre SynchronousQueue
A ideia básica de uma transferência síncrona é bastante simples e, no entanto, contra-intuitiva (ou seja, a intuição ou o bom senso dizem que está errado): você pode adicionar um elemento a uma fila se e somente se outro thread receber o elemento no mesmo tempo. Em outras palavras, uma fila síncrona não pode conter tarefas, pois assim que uma nova tarefa chega, a thread em execução já a pegou .
Quando uma nova tarefa entra na fila, se houver um thread ativo livre no pool, ela seleciona a tarefa. Se todos os threads estiverem ocupados, um novo thread será criado.
Um pool em cache começa com zero threads e pode aumentar potencialmente para Integer.MAX_VALUE threads. Essencialmente, o tamanho de um conjunto de encadeamentos em cache é limitado apenas pelos recursos do sistema.
Para conservar os recursos do sistema, os pools de encadeamentos em cache removem os encadeamentos que estão ociosos por um minuto.
Vamos ver como funciona na prática. Criaremos uma classe de tarefa que modela uma solicitação do usuário:
public class Task implements Runnable {
int taskNumber;
public Task(int taskNumber) {
this.taskNumber = taskNumber;
}
@Override
public void run() {
System.out.println("Processed user request #" + taskNumber + " on thread " + Thread.currentThread().getName());
}
}
No método main , criamos newCachedThreadPool e adicionamos 3 tarefas para execução. Aqui imprimimos o status do nosso serviço (1) .
Em seguida, pausamos por 30 segundos, iniciamos outra tarefa e exibimos o status (2) .
Depois disso, pausamos nosso thread principal por 70 segundos, imprimimos o status (3) , adicionamos novamente 3 tarefas e imprimimos novamente o status (4) .
Em locais onde exibimos o status imediatamente após adicionar uma tarefa, primeiro adicionamos uma suspensão de 1 segundo para saída atualizada.
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 3; i++) {
executorService.submit(new Task(i));
}
TimeUnit.SECONDS.sleep(1);
System.out.println(executorService); //(1)
TimeUnit.SECONDS.sleep(30);
executorService.submit(new Task(3));
TimeUnit.SECONDS.sleep(1);
System.out.println(executorService); //(2)
TimeUnit.SECONDS.sleep(70);
System.out.println(executorService); //(3)
for (int i = 4; i < 7; i++) {
executorService.submit(new Task(i));
}
TimeUnit.SECONDS.sleep(1);
System.out.println(executorService); //(4)
executorService.shutdown();
E aqui está o resultado:
Solicitação de usuário processada nº 1 no thread pool-1-thread-2
Solicitação de usuário processada nº 2 no thread pool-1-thread-3
(1) java.util.concurrent .ThreadPoolExecutor@f6f4d33[Em execução, tamanho do conjunto = 3, encadeamentos ativos = 0, tarefas enfileiradas = 0, tarefas concluídas = 3]
Solicitação de usuário processada nº 3 no encadeamento pool-1-thread-2
(2) java.util.concurrent. ThreadPoolExecutor@f6f4d33[Em execução, tamanho do pool = 3, encadeamentos ativos = 0, tarefas enfileiradas = 0, tarefas concluídas = 4] (3) java.util.concurrent.ThreadPoolExecutor@f6f4d33[Em execução,
tamanho do conjunto = 0, encadeamentos ativos = 0 , tarefas enfileiradas = 0, tarefas concluídas = 4]
Solicitação de usuário processada nº 4 no thread pool-1-thread-4
Solicitação de usuário processada nº 5 no thread pool-1-thread-5
Solicitação de usuário processada nº 6 no thread pool-1-thread-4
(4) java.util.concurrent.ThreadPoolExecutor@f6f4d33[Em execução, tamanho do pool = 2, threads ativos = 0, tarefas enfileiradas = 0, tarefas concluídas = 7]
Vamos a cada uma das etapas:
Etapa | Explicação |
---|---|
1 (após 3 tarefas concluídas) | Criamos 3 threads e 3 tarefas foram executadas nesses três threads. Quando o status é exibido, todas as 3 tarefas são concluídas e os encadeamentos estão prontos para executar outras tarefas. |
2 (após pausa de 30 segundos e execução de outra tarefa) | Após 30 segundos de inatividade, os encadeamentos ainda estão ativos e aguardando tarefas. Outra tarefa é adicionada e executada em um thread retirado do pool dos threads ativos restantes. Nenhum novo thread foi adicionado ao pool. |
3 (após uma pausa de 70 segundos) | Os encadeamentos foram removidos do pool. Não há threads prontos para aceitar tarefas. |
4 (depois de executar mais 3 tarefas) | Depois que mais tarefas foram recebidas, novos tópicos foram criados. Desta vez, apenas dois threads conseguiram processar 3 tarefas. |
Bem, agora você está familiarizado com a lógica de outro tipo de serviço executor.
Por analogia com outros métodos da classe utilitária Executors , o método newCachedThreadPool também possui uma versão sobrecarregada que recebe um objeto ThreadFactory como argumento.
GO TO FULL VERSION