introdução
com o aumento do número de núcleos disponíveis nos processadores hoje no dia, ao longo Com a crescente necessidade de obter maior desempenho, as APIs multi-threaded estão se tornando bastante populares. Java proporciona su propio marco de subprocesos múltiples llamado Marco del ejecutor.
¿Qué es Executor Framework?
Executor Framework contiene una serie de componentes que se utilizan para administrar de manera eficiente los subprocesos de trabalho. A API do Executor Decople a execução da tarefa real de tarefa que será executada por Executors
. Este design é uma das implementações do padrão de produtor-consumidor.
O java.util.concurrent.Executors
Fornecer métodos de fábrica que são usados para criar ThreadPools
de threads de trabalho.
Para usar a estrutura do executor, precisamos criar um desses grupos subprocessos e enviar a tarefa para execução. É o trabalho do programa-quadro do Executor e executa as tarefas enviadas e retornam os resultados do grupo subprocesso.
Uma pergunta básica que vem à mente é por que precisamos de tais grupos de subprocessos quando podemos criar objetos de java.lang.Thread
ou implementar Runnable
/ Callable
interfaces para obter paralelismo?
A resposta é reduzida a dois fatos básicos:
- A criação de uma nova linha para uma nova tarefa envolve uma sobrecarga de criação e desmontagem do segmento. A gestão do ciclo de vida deste tópico aumenta significativamente o tempo de execução.
- Adicione um novo segmento para cada processo sem qualquer tipo de limitação leva à criação de um grande número de threads. Esses subprocessos ocupam memória e causam recursos de resíduos. A CPU começa a dedicar muito tempo para alterar o contexto quando cada linha é trocada e outra linha é inserida para execução.
Todos esses fatores reduzem o desempenho do sistema. Grupos de subprocessamento superam esse problema, mantendo os tópicos vivos e reutilizando-os. Qualquer excesso de tarefas que fluem de que subprocessas no grupo podem manipular são mantidas em um Queue
. Uma vez que qualquer um dos tópicos for liberado, eles ocupam a próxima tarefa dessa fila. Esta fila de tarefas é essencialmente ilimitada para os executores prontos para uso fornecido pelo JDK.
Tipos de executores
Agora que temos uma boa ideia do que é um executor, vamos Dê uma olhada também os diferentes tipos de executores.
singlethreadexecuctor
Este executor do grupo subprocesso tem apenas um thread. É usado para executar tarefas sequencialmente. Se o thread morre devido a uma exceção durante a execução de uma tarefa, uma nova linha é criada para substituir os fios antigos e as tarefas subseqüentes são executadas no novo.
ExecutorService executorService = Executors.newSingleThreadExecutor()
federstthreadpool (n)
Como o nome indica, é um grupo de subprocessos de um número fixo de subprocessos. As tarefas enviadas para o executor são executadas pelo tópico n
e se houver mais tarefas armazenadas em um LinkedBlockingQueue
. Este número é geralmente o número total de subprocessos suportados pelo processador subjacente.
ExecutorService executorService = Executors.newFixedThreadPool(4);
cachedthreadpool
Este grupo de subprocessos é usado principalmente quando Existem muitas tarefas paralelas de curta duração para executar. Ao contrário do grupo de encadeamentos fixos, o número de subprocessos neste grupo de executores não é limitado. Se todos os tópicos estiverem ocupados executando algumas tarefas e uma nova chegada, o grupo criará e adicionará um novo subprocesso ao executor. Assim que um dos subprocessos é livre, ele retomará a execução das novas tarefas. Se um thread permanecer inativo por sessenta segundos, eles serão concluídos e removidos do cache.
No entanto, se não for administrado corretamente ou as tarefas não forem curtas, o grupo de subprocidades terá muitos ativos subprocessos. Isso pode levar à eliminação de recursos e, portanto, em uma queda de desempenho.
ExecutorService executorService = Executors.newCachedThreadPool();
Fiscal programado
Este executor é usado quando ter uma tarefa que precisa ser executada em intervalos regulares ou se quisermos atrasar uma determinada tarefa.
ScheduledExecutorService scheduledExecService = Executors.newScheduledThreadPool(1);
As tarefas podem ser programadas em ScheduledExecutor
Usando qualquer um dos dois métodos scheduleAtFixedRate
ou scheduleWithFixedDelay
.
scheduledExecService.scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
scheduledExecService.scheduleWithFixedDelay(Runnable command, long initialDelay, long period, TimeUnit unit)
A principal diferença entre os dois métodos é sua interpretação do atraso entre as execuções consecutivas de trabalho programado.
scheduleAtFixedRate
Executa a tarefa com um intervalo fixo, independentemente de quando a tarefa anterior concluída.
scheduleWithFixedDelay
Iniciará a contagem regressiva apenas após a conclusão da tarefa atual.
Compreender o objeto futuro
Você pode acessar o resultado da tarefa enviada para execução para um Executor usando o objeto retornado pelo executor. O futuro pode ser considerado como uma promessa feita pelo executor para o chamador.
Future<String> result = executorService.submit(callableTask);
Uma tarefa enviada para o executor, como anterior, é assíncrona, Ou seja, a execução do programa não espera que a execução da tarefa se mova para o próximo passo. Em vez disso, sempre que a execução da tarefa estiver concluída, ele é estabelecido neste objeto Future
pelo Albacea.
O chamador pode continuar a executar o programa principal e Quando o resultado da tarefa enviada é necessário, você pode chamar .get()
neste objeto Future
. Se a tarefa for concluída, o resultado é imediatamente retornado ao chamador ou, caso contrário, o chamador será bloqueado até que o executor conclua a execução e o resultado for calculado.
Se a pessoa que chama não puder pagar indefinidamente antes Recuperando o resultado, essa espera também pode ser cronometrada. Isso é conseguido pelo método Future.get(long timeout, TimeUnit unit)
que produz um TimeoutException
Se o resultado não for devolvido dentro do período estipulado. O chamador pode lidar com essa exceção e continuar com a execução subseqüente do programa.
Se houver uma exceção ao executar a tarefa, o método GET produzirá um ExecutionException
.
Algo importante sobre o resultado retornado pelo método é que ele é retornado somente se a tarefa enviada implementa java.util.concurrent.Callable
. Se a tarefa implementa a interface Runnable
, a chamada para .get()
retornará Uma vez o A tarefa está concluída.
Outro método importante é o método . Este método é usado para cancelar a execução de uma tarefa enviada. Se a tarefa já estiver em execução, o executor tentará interromper a execução da tarefa se o sinalizador mayInterruptIfRunning
O sinalizador é passado como true
.
exemplo: criar e executar um executor simples
Agora vamos criar uma tarefa e tentar executá-lo em um executor de grupo fixo:
public class Task implements Callable<String> { private String message; public Task(String message) { this.message = message; } @Override public String call() throws Exception { return "Hello " + message + "!"; }}
o Task
Aula implementa Callable
e é parametrizado para String
tipo. Também é declarado jogando Exception
. Essa capacidade de lançar uma exceção ao executor e o executor retornar essa exceção ao chamador é de grande importância porque ajuda a pessoa a chamar o status de execução da tarefa.
Agora, execuamos esta tarefa:
public class ExecutorExample { public static void main(String args) { Task task = new Task("World"); ExecutorService executorService = Executors.newFixedThreadPool(4); Future<String> result = executorService.submit(task); try { System.out.println(result.get()); } catch (InterruptedException | ExecutionException e) { System.out.println("Error occured while executing the submitted task"); e.printStackTrace(); } executorService.shutdown(); }}
aqui nós criamos um executador FixedThreadPool
com uma contagem de 4 threads como esta demonstração é desenvolvida em um processador de quatro núcleos. O número de subprocessos pode ser maior que os núcleos do processador se as tarefas executadas realizam operações de E / S consideráveis ou passar o tempo aguardando recursos externos.
Instantemos o Task
Classe e levá-lo ao executor para execução. O resultado é retornado pelo objeto Future
, que então imprime na tela.
Deixe-nos executar o ExecutorExample
e verifique sua saída:
Hello World!
Como esperado, a tarefa adiciona a saudação “Hello” e retorna o resultado através do Future
objeto.
Finalmente, chamamos no momento do objeto executorService
para concluir todos os encadeamentos e retornar recursos para o sistema operacional.
o .shutdown()
O método é atualmente a conclusão das tarefas atualmente enviadas para o executor. No entanto, se o requisito estiver fechando imediatamente o executor sem esperar, podemos usar o .shutdownNow()
Método no lugar.
Qualquer tarefa de execução pendente será retornada em um objeto .
Também podemos criar essa mesma tarefa implementando a interface Runnable
public class Task implements Runnable{ private String message; public Task(String message) { this.message = message; } public void run() { System.out.println("Hello " + message + "!"); }}
Há um par De mudanças importantes aqui quando implementamos o runnable.
- O resultado da execução da tarefa não pode ser retornado do método
run()
. Portanto, estamos imprimindo diretamente daqui. - o
run()
o método não está configurado para iniciar qualquer exceção marcada.
conclusão
O múltiplo subprocesso está se tornando cada vez mais comum, já que a velocidade do relógio do processador é difícil de aumentar. No entanto, lidar com o ciclo de vida de cada fio é muito difícil devido à complexidade envolvida.
Neste artigo, demonstramos um múltiplo, mas simples quadro de discussão múltipla, framework executor e explicou seus componentes diferentes. Também damos uma olhada em diferentes exemplos de criação e execução de tarefas em um executor.
Como sempre, o código deste exemplo pode ser encontrado no GitHub.