Antecedentes técnicos do pool de threads
Na programação orientada a objetos, criar e destruir objetos é demorado, porque a criação de um objeto requer recursos de memória ou outros recursos. Isso é ainda mais em Java, onde a máquina virtual tentará rastrear cada objeto para que possa ser coletado de lixo após a destruição do objeto.
Portanto, uma maneira de melhorar a eficiência dos programas de serviço é minimizar o número de momentos de criação e destruição de objetos, especialmente alguns objetos que consomem recursos, criação e destruição de objetos que consomem recursos. Como usar os objetos existentes para servir é uma questão -chave que precisa ser resolvida. De fato, essa é a razão pela qual algumas tecnologias de "recursos de agrupamento" são produzidas.
Por exemplo, muitos componentes comuns comumente vistos no Android são geralmente inseparáveis do conceito de "pool", como várias bibliotecas de carregamento de imagens e bibliotecas de solicitação de rede. Mesmo que o meaasge no mecanismo de mensagens do Android use meaasge.obtain (), é o objeto no pool de meaasge usado, então esse conceito é muito importante. A tecnologia de pool de threads introduzida neste artigo também está em conformidade com essa idéia.
Vantagens do pool de threads:
1. Reutilização de fios no pool de threads para reduzir a sobrecarga de desempenho causada pela criação e destruição de objetos;
2. Pode controlar efetivamente o número máximo de simultaneidade de encadeamentos, melhorar a utilização de recursos do sistema e evitar a concorrência excessiva de recursos e evitar o bloqueio;
3. Capacidade de executar um gerenciamento simples de vários threads, tornando o uso de threads simples e eficientes.
Executor da estrutura do pool de threads
O pool de threads em Java é implementado através da estrutura do executor. A estrutura do executor inclui classes: Executor, Executores, ExecutorService, ThreadpoolExecutor, Callable and Future, Uso FutureTask, etc.
Executor: Existe apenas um método para todas as interfaces do pool de threads.
Executor de interface pública {void Execute (comando runnable); }ExecutorService: Adicionar o comportamento do executor é a interface mais direta da classe de implementação do executor.
Executores: fornece uma série de métodos de fábrica para criar o pool de threads e os pools de threads retornados implementam a interface ExecorService.
ThreadpoolExecutor: a classe de implementação específica de pools de threads. Os vários pools de threads geralmente usados são implementados com base nesta classe. O método de construção é o seguinte:
public threadpoolExecutor (int CorePoolSize, int maximumPoolSize, Longo KeepAliveTime, Unidade Timeunit, BlockingQueue <uncrnable> WorkQueue) {this (CorePoolSize, MaximumPoolSize, KeepAlivETime, unidade, Workqueue, Executores.DefaultThreadFactory (), DefaLiVandler);CorePoolSize: o número de encadeamentos principais no pool de threads e o número de threads em execução no pool de threads nunca excederão o CorePoolSize e podem sobreviver por padrão. Você pode definir o AllowCorethReadTimeout como TRUE, o número de threads principais é 0 e o KeepAliveTime controla o tempo de tempo limite de todos os threads.
MaximumPoolSize: o número máximo de threads permitidos pelo pool de threads;
KeepaliveTime: refere -se ao tempo limite quando o encadeamento ocioso termina;
Unidade: é uma enumeração, representando a unidade do KeepaliveTime;
Trabalho: representa o BlockingQueue <Fila Runnable que armazena a tarefa.
BlockingQueue: BlockingQueue é uma ferramenta usada principalmente para controlar a sincronização do encadeamento em java.util.concurrent. Se o BlockQueue estiver vazio, a operação de buscar algo do BlockingQueue será bloqueada e não será despertada até que o BlockingQueue entre na coisa. Da mesma forma, se o BlockingQueue estiver cheio, qualquer operação que tente armazenar as coisas será bloqueada e não será despertada para continuar as operações até que haja espaço no BlockingQueue. As filas de bloqueio são frequentemente usadas em cenários de produtores e consumidores. Os produtores são threads que adicionam elementos às filas, e os consumidores são threads que retiram elementos das filas. Uma fila de bloqueio é o contêiner onde o produtor armazena elementos, e o consumidor leva apenas elementos do contêiner. As classes de implementação específicas incluem LinkedBlockockQueue, ArrayBlockockQueed, etc. Geralmente, o bloqueio interno e o despertar são alcançados através da bloqueio e condição (aprendizado e uso de bloqueios e condições de exibição).
O processo de trabalho do pool de threads é o seguinte:
Quando o pool de threads foi criado pela primeira vez, não havia thread dentro. A fila de tarefas é passada como um parâmetro. No entanto, mesmo que haja tarefas na fila, o pool de threads não as executará imediatamente.
Ao adicionar uma tarefa chamando o método Execute (), o pool de threads fará o seguinte julgamento:
Se o número de threads em execução for menor que o CorePoolSize, crie um thread para executar essa tarefa imediatamente;
Se o número de threads em execução for maior ou igual ao CorePoolSize, coloque essa tarefa na fila;
Se a fila estiver cheia neste momento e o número de threads em execução for menor que o MaximumPoolSize, você ainda precisará criar um thread não essencial para executar a tarefa imediatamente;
Se a fila estiver cheia e o número de threads em execução for maior ou igual a MaximumPoolSize, o pool de threads lançará uma exceção rejeição deexecutionException.
Quando um thread conclui uma tarefa, é necessária a próxima tarefa da fila para executar.
Quando um thread não tem nada a fazer e, após um certo período de tempo (KeepaliveTime), o pool de threads julgará que, se o número de threads atualmente em execução for maior que o CorePoolSize, o thread será interrompido. Portanto, depois que todas as tarefas do pool de threads são concluídas, ele acabará diminuindo para o tamanho do CorePoolSize.
Criação e uso de piscinas de threads
O pool de threads de geração usa o método estático dos executores da classe de ferramentas. A seguir, são apresentados vários pools de fios comuns.
SinglethreadExecutor: Tópico de fundo único (a fila de buffer é ilimitada)
public Static ExecorService NewsingLethReadExecutor () {Retorne novo FinalizedElegateExExExerService (novo ThreadPoolExecutor (1, 1, 0L, timeUnit.millisEconds, novo LinkedBlockingQueue <unnable> ())); }Crie um único pool de threads. Este pool de threads tem apenas um encadeamento principal funcionando, que é equivalente a um único thread que executa todas as tarefas no Serial. Se esse thread exclusivo terminar devido à exceção, haverá um novo thread para substituí -lo. Este pool de threads garante que a ordem de execução de todas as tarefas seja executada na ordem do envio da tarefa.
FILLTHREADPOOL: Somente o pool de roscas dos fios do núcleo, com tamanho fixo (a fila do buffer é ilimitada).
public static executorService newfixedthreadpool (int nthreads) {
Retorne o novo ThreadpoolExecutor (Nthreads, Nthreads,
0l, timeUnit.millisEconds,
new LinkedBlockingQueue <unnable> ());
}
Crie um pool de threads de tamanho fixo. Cada vez que uma tarefa é enviada, um encadeamento é criado até que o encadeamento atinja o tamanho máximo do pool de encadeamentos. O tamanho do pool de threads permanece o mesmo quando atinge seu valor máximo. Se um thread terminar devido a uma exceção de execução, o pool de threads adicionará um novo thread.
CachedThreadpool: Pool de threads ilimitado, pode executar a reciclagem automática de threads.
public static executorService newcachedThreadpool () {return new ThreadpoolExecutor (0, Integer.max_value, 60L, timeunit.seconds, novo síncronoiouse <dunnable> ()); }Se o tamanho do pool de threads exceder o thread necessário para processar a tarefa, alguns dos threads inativos (nenhuma execução de tarefas em 60 segundos) serão reciclados. Quando o número de tarefas aumenta, esse pool de threads pode adicionar de forma inteligente novos threads para processar a tarefa. Este pool de threads não limita o tamanho do pool de threads, que depende inteiramente do tamanho máximo de encadeamento que o sistema operacional (ou JVM) pode criar. O síncrono é uma fila de bloqueio com tampão de 1.
ScheduledThreadpool: um pool de encadeamentos do núcleo com pool de rosca fixo, tamanho ilimitado. Este pool de threads suporta a necessidade de executar tarefas periodicamente e periodicamente.
Public Static ExecorService NewsChedulEdThreadpool (int CorePoolSize) {Return New ScheduledThreadpool (CorePoolsize, Integer.max_value, Default_keepalive_millis, milissegundos, New AtardWorkQueue ()); }Crie um pool de threads que execute tarefas periodicamente. Se ocioso, o pool de threads não essencial será reciclado no Default_keepalivemillis Time.
Existem dois métodos mais usados para enviar tarefas em pools de threads:
executar:
ExecutorService.execute (executável runable);
Enviar:
FutureTask Task = ExecutorService.submit (Runnable Runnable);
FutureTask <T> tarefa = ExecutorService.subMit (Runnable Runnable, t Resultados);
FutureTask <T> tarefa = ExecutorService.submit (Callable <T> Callable);
O mesmo se aplica à implementação do envio (chamável chamável) e o mesmo se aplica ao envio (executável runnable).
public <T> FUTURO <T> Enviar (Callable <T> Task) {if (tarefa == null) lança novo NullPointerException (); FutureTask <T> ftask = newTaskfor (tarefa); executar (ftask); devolver ftask;}Pode -se observar que o envio é uma tarefa que retorna um resultado e retornará um objeto FutureTask, para que o resultado possa ser obtido através do método get (). A chamada final a ser enviada também é executada (executável executável). Enviar apenas encapsula o objeto chamável ou executável em um objeto FutureTask. Como o FutureTask é executável, ele pode ser executado no Execute. Para como objetos chamáveis e executáveis são encapsulados em objetos FutureTask, consulte o uso de FutureTask chamáveis, Future, FutureTask.
O princípio da implementação do pool de threads
Se você falar apenas sobre o uso de pools de threads, este blog não terá grande valor. Na melhor das hipóteses, é apenas um processo de familiarizar-se com a API relacionada ao executor. O processo de implementação do pool de threads não usa a palavra -chave sincronizada, mas usa filas voláteis, de bloqueio e síncrono (bloqueio), classes relacionadas atômicas, FutureTask etc., porque o último tem melhor desempenho. O processo de compreensão pode aprender bem a idéia de controle simultâneo no código -fonte.
As vantagens do agrupamento de threads mencionadas no início estão resumidas da seguinte forma:
Reutilização de thread
Controle o número máximo de concorrências
Gerenciar threads
1. Processo de multiplexação de threads
Para entender o princípio da multiplexação por threads, você deve primeiro entender o ciclo de vida do thread.
Durante o ciclo de vida de um fio, ele precisa passar por cinco estados: novo, fugitivo, correndo, bloqueado e morto.
O thread cria um novo thread através do novo. Esse processo é inicializar algumas informações sobre threads, como nome de encadeamento, ID, grupo ao qual o thread pertence etc., que pode ser considerado apenas um objeto comum. Depois de chamar o start do thread (), a máquina virtual Java cria uma pilha de chamadas de método e um contador de programas para ela e, ao mesmo tempo, foi iniciado para True, e haverá uma exceção ao chamar o método de início.
O encadeamento nesse estado não começa a ser executado, mas significa apenas que o thread pode ser executado. Quanto a quando o thread começa a ser executado, depende do agendador na JVM. Quando o thread obtém a CPU, o método run () será chamado. Não chame o método Run () Thread (). Em seguida, alterne entre o bloqueio pronto de acordo com a programação da CPU, até que o método Run () termine ou outros métodos parem o thread e entre no estado morto.
Portanto, o princípio da implementação da reutilização do encadeamento deve ser manter o thread vivo (pronto, funcionando ou bloqueando). Em seguida, vamos dar uma olhada em como o ThreadpoolExector implementa a reutilização do thread.
A principal classe de trabalhadores no ThreadpoolExecutor controla a reutilização do encadeamento. Dê uma olhada no código simplificado da classe do trabalhador, por isso é fácil de entender:
O trabalhador da classe final privado implementa Runnable {Final Threads Thread; Runnable FirstSask; Worker (Runnable FirstTask) {this.firstTask = FirstTask; this.Thread = getThreadFactory (). Newthread (this);} public void run () {runworker (this);}} final vot runker (Worker Worker W) {RunWorker (this);} FinalASt RunTorker (Workerw) {Runworker (runworker); null; while (tarefa! = null || (tarefa = gettask ())! = null) {task.run ();}}O trabalhador é executável e tem um tópico ao mesmo tempo. Este tópico é o thread a ser aberto. Ao criar um novo objeto de trabalhador, um novo objeto de encadeamento é criado ao mesmo tempo e o próprio trabalhador é passado para o TTHREAD como um parâmetro. Dessa forma, quando o método START () do thread é chamado, o método RUN () do trabalhador está realmente em execução. Então, em Runworker (), há um loop de tempo, que continua recebendo o objeto executável de gettask () e o executa em sequência. Como GetTask () obtém o objeto executável?
Ainda o código simplificado:
Private Runnable getTask () {if (alguns casos especiais) {return null; } Runnable r = workQueue.take (); retorna r;}Este trabalho de trabalho é a fila BlockingQueue que armazena tarefas ao inicializar o ThreadPoolExecutor. A fila armazena as tarefas executáveis a serem executadas. Como o BlockingQueue é uma fila de bloqueio, BlockingQueue.Take () fica vazio e entra no estado de espera até que um novo objeto no BlockingQueue seja adicionado para acordar o fio bloqueado. Portanto, em geral, o método RUN () do encadeamento não terminará, mas continuará a executar tarefas executáveis da WorkQueue, que atinge o princípio da reutilização do thread.
2. Controle o número máximo de simultaneidade
Então, quando é executável colocado em trabalho? Quando o trabalhador é criado? Quando o tópico do trabalhador é chamado START () para abrir um novo thread para executar o método Worker Run ()? A análise acima mostra que o RunWorker () no trabalhador executa as tarefas uma após a outra, em série, então como a simultaneidade se manifesta?
É fácil pensar que você realizará algumas das tarefas acima quando executar (executável executável). Vamos ver como é feito na execução.
executar:
Código simplificado
public void execute (comando runnable) {if (command == null) lança new nullPointerException (); int c = ctl.get (); // número atual de threads <corePoolSizeIF (WorkerCountOf (c) <CorePoolSize) {// inicia diretamente um novo thread. if (addWorker(command, true))return;c = ctl.get();}// Number of active threads>= corePoolSize// runState is RUNNING && Queue not full if (isRunning(c) && workQueue.offer(command)) {int recheck = ctl.get();// Check again whether it is RUNNING status// If the non-RUNNING status is removed from the workQueue and reject if (! isrunning (rechoque) && remover (comando)) rejeitar (comando); // use a estratégia especificada pelo pool de threads para rejeitar a tarefa // dois casos: // 1.AddWorker:
Código simplificado
AddWorker Boolean Private (Runnable FirstTask, Core Boolean) {int wc = WorkerCountOf (c); if (wc> = (Core? CorePoolSize: MaximumPoolSize)) {return false;} w = novo trabalhador (Firstask); Thread T = W.Thread; T.Start ();}De acordo com o código, vejamos a situação acima mencionada de adicionar tarefas durante o trabalho do pool de threads:
* Se o número de threads em execução for menor que o CorePoolSize, crie um thread para executar essa tarefa imediatamente;
* Se o número de threads em execução for maior ou igual ao CorePoolSize, coloque essa tarefa na fila;
* Se a fila estiver cheia neste momento e o número de threads em execução for menor que o MaximumPoolSize, você ainda precisará criar um thread não essencial para executar a tarefa imediatamente;
* Se a fila estiver cheia e o número de threads em execução for maior ou igual ao MaximumPoolSize, o pool de threads lançará uma exceção rejeição deexecutionException.
É por isso que o assíncrono do Android é executado em paralelo e excede o número máximo de tarefas e lança rejeiçãoExecutionException. Para detalhes, consulte a versão mais recente da interpretação do código -fonte do AsyncTask e o lado sombrio do AsyncTask
Se um novo thread for criado com sucesso através do AddWorker, start () e use FirstTask como a primeira tarefa executada em run () neste trabalhador.
Embora a tarefa de cada trabalhador seja o processamento em série, se vários trabalhadores forem criados, porque compartilham uma câmara de trabalho, eles serão processados em paralelo.
Portanto, o número máximo de simultaneidade é controlado de acordo com o CorePoolSize e MaximumPoolSize. O processo geral pode ser representado pela figura abaixo.
A explicação e as imagens acima podem ser bem compreendidas.
Se você está envolvido no desenvolvimento do Android e está familiarizado com os princípios do manipulador, pode pensar que essa imagem é bastante familiar. Alguns dos processos são muito semelhantes aos usados pelo Handler, Looper e Meaasge. Handler.send (Mensagem) é equivalente a executar (RunNuble). A fila de meaasge mantida em Looper é equivalente a BlockingQueue. No entanto, você precisa manter essa fila por sincronização. A função loop () em Looper Loops para retirar o meaasge da fila de meaasge e o trabalho de corrida () no trabalhador é continuamente executável a partir do BlockingQueue.
3. Gerencie threads
Através do pool de threads, podemos gerenciar a reutilização do encadeamento, controlar o número da simultaneidade e destruir processos. A concorrência de reutilização e controle de threads foi discutida acima, e o processo de gerenciamento de threads foi intercalado nele, o que é fácil de entender.
Existe uma variável atomicinteger CTL no ThreadPoolExecutor. Dois conteúdos são salvos através desta variável:
O número de todos os threads. Cada encadeamento está em um estado em que os 29 bits inferiores de roscas são armazenados e os 3 bits mais altos do RunState são armazenados. Valores diferentes são obtidos através de operações de bits.
Atomicinteger final privado ctl = new AtomicInteger (ctlof (em execução, 0)); // Obtenha o estado do estado privado estático int runStateOf (int c) {return c & ~ capacidade;} // obtenha o número de trabalhadores privados privados staCountOf (int c) {return c & capacidade;} // obtenha os trabalhadores privados stant stantoftOf (int c) {retorna c &;} // obtenha os trabalhadores privados O tópico está executando Isrunning Boolean Private estático (int c) {return C <Shutdown;}Aqui, o processo de desligamento do pool de threads é analisado principalmente por desligamento e desligamento (). Primeiro de tudo, o pool de threads possui cinco estados para controlar a adição e a execução das tarefas. Os três tipos principais a seguir são introduzidos:
Status de execução: o pool de threads está sendo executado normalmente e pode aceitar novas tarefas e processar tarefas na fila;
Status de desligamento: nenhuma nova tarefa é aceita, mas as tarefas na fila serão executadas;
Status de parada: nenhuma nova tarefa é aceita mais e o desligamento da tarefa não é processado na fila. Esse método definirá o RunState para desligar e encerrará todos os threads inativos, enquanto os threads que ainda estão funcionando não são afetados; portanto, a pessoa da tarefa na fila será executada.
O método de desligamento define o RunState para parar. A diferença entre o método de desligamento, esse método encerrará todos os threads; portanto, as tarefas na fila não serão executadas.
Resumo: Através da análise do código -fonte ThreadPoolExecutor, temos um entendimento geral do processo de criação de pools de threads, adicionando tarefas e execução de pools de threads. Se você estiver familiarizado com esses processos, será mais fácil usar os pools de threads.
Os alguns dos controle de simultaneidade aprendidos com ele e o uso do processamento de tarefas dos modelos de consumidores de produtores serão de grande ajuda para entender ou resolver outros problemas relacionados no futuro. Por exemplo, o mecanismo de manipulador no Android e a fila Messager em Looper também são bons para usar um Blookue para lidar com isso. Este é o ganho de ler o código -fonte.
O exposto acima está as informações que classificam o pool de threads java. Continuaremos a adicionar informações relevantes no futuro. Obrigado pelo seu apoio a este site!