Notas após a leitura de "The Art of Java Concurrent Programming" - Explicação detalhada de FutureTask (Capítulo 10)

Notas após a leitura de "The Art of Java Concurrent Programming" - Explicação detalhada de FutureTask (Capítulo 10)

1. Introdução ao FutureTask

Além de implementar a interface, FutureTask Futuretambém implementa Runnablea interface. Portanto, FutureTask pode ser entregue ao Executor para execução ou pode ser executado diretamente pelo thread de chamada ( FutureTask.run()).

De acordo com o tempo em que o método FutureTask.run() é executado, a FutureTask pode estar nos três estados a seguir:

  1. Não iniciado . Antes da execução do método FutureTask.run(), a FutureTask não é iniciada. Quando uma FutureTask é criada e o método FutureTask.run() não é executado, a FutureTask não é iniciada.
  2. começou . Durante a execução do método FutureTask.run(), a FutureTask está no estado iniciado.
  3. concluído . O método FutureTask.run() termina normalmente após a execução, ou é cancelado (FutureTask.cancel(…)), ou uma exceção é lançada quando o método FutureTask.run() é executado e termina de forma anormal, e o FutureTask está em uma conclusão estado.

FutureTaskUm diagrama esquemático da transição de estado:

imagem-20220118160357680

Para o método FutureTask.get() :

  • Quando o FutureTask não é iniciado ou iniciado, a execução do método FutureTask.get() fará com que o thread de chamada seja bloqueado
  • Quando FutureTask está no estado concluído, a execução do método FutureTask.get() fará com que o thread de chamada retorne o resultado imediatamente ou lance uma exceção.

Para o método FutureTask.cancel() :

  • Quando a FutureTask não for iniciada, a execução do método FutureTask.cancel() fará com que a tarefa nunca seja executada
  • Quando o FutureTask está no estado iniciado, a execução do método FutureTask.cancel(true) tentará parar a tarefa interrompendo o thread que está executando a tarefa
  • Quando o FutureTask está no estado iniciado, a execução do método FutureTask.cancel(false) não afetará o thread que está executando a tarefa (deixe a tarefa em execução ser concluída)
  • Quando a FutureTask estiver no estado concluído, a execução do método FutureTask.cancel(…) retornará false.

getDiagrama esquemático do método e cancelexecução do método:

imagem-20220118161125457

2. O uso de FutureTask

Você pode entregar o FutureTask para o Executor para execução, você também pode retornar um FutureTask através do método ExecutorService.submit(…) e então executar o método FutureTask.get() ou o método FutureTask.cancel(…). Além disso, você também pode usar o FutureTask sozinho

Quando um thread precisa esperar que outro thread conclua uma tarefa antes de continuar a execução, você pode usar FutureTask

por exemplo: Assumindo que existem vários threads executando várias tarefas, cada tarefa pode ser executada apenas uma vez, no máximo. Quando vários threads tentam executar a mesma tarefa ao mesmo tempo, apenas um thread tem permissão para executar a tarefa e os outros threads precisam aguardar a execução da tarefa antes de continuar.

código mostra como abaixo:

/**
 * @author xppll
 * @date 2022/1/18 16:21
 */
public class FutureTaskTest {
    
    
    private final ConcurrentMap<Object, Future<String>> taskCache
            = new ConcurrentHashMap<Object, Future<String>>();

    private String executionTask(final String taskName) {
    
    
        while (true) {
    
    
            Future<String> future = taskCache.get(taskName);
            if (future == null) {
    
    
                Callable<String> task = new Callable<String>() {
    
    
                    @Override
                    public String call() throws Exception {
    
    
                        return taskName;
                    }
                };
                FutureTask<String> futureTask = new FutureTask<String>(task);
                future = taskCache.putIfAbsent(taskName, futureTask);
                if (future == null) {
    
    
                    future = futureTask;
                    futureTask.run();
                }
                try {
    
    
                    return future.get();
                } catch (InterruptedException | ExecutionException e) {
    
    
                    taskCache.remove(taskName, future);
                    e.printStackTrace();
                }

            }
        }
    }

    public static void main(String[] args) {
    
    

    }
}

O diagrama esquemático da execução do código acima é o seguinte:

imagem-20220118162553379

Quando dois encadeamentos tentam executar a mesma tarefa ao mesmo tempo, se o encadeamento 1 executar 1.3 e o encadeamento 2 executar 2.1, o encadeamento 2 aguardará em 2.2 até que o encadeamento 1 execute 1.4 antes que o encadeamento 2 possa iniciar a partir de 2.2 (FutureTask.get( ) )retornar

3. Implementação do FutureTask

FutureTaskA implementação é baseada em AbstractQueuedSynchronizer(AQS). AQS é uma estrutura de sincronização que fornece mecanismos gerais para gerenciar atomicamente o estado de sincronização, bloquear e ativar threads e manter filas de threads bloqueados.

Para AQS detalhado, consulte: Notas após a leitura de "A Arte da Programação Concorrente em Java" - Capítulo 5 Bloqueios em Java

Todo sincronizador implementado com base no AQS conterá dois tipos de operações:

  1. pelo menos uma acquireação.
    • Esta operação bloqueia o thread de chamada, a menos que/até que o estado do AQS permita que o thread continue em execução.
    • A operação de aquisição de FutureTask é uma chamada de método get()/get(long timeout, TimeUnit unit).
  2. pelo menos uma releaseação.
    • Esta operação altera o estado do AQS, o que permite que um ou mais threads bloqueados sejam desbloqueados.
    • A operação de liberação de FutureTask inclui o método run() e o método cancel(...).

baseado emComposição sobre herançaDe acordo com o princípio, FutureTask declara uma subclasse privada interna herdada de AQS Sync, e as chamadas para todos os métodos públicos de FutureTask serão delegadas a esta subclasse interna.

AQS é fornecida como a classe básica do "padrão de método modelo" para a subclasse interna Sync de FutureTask. Esta subclasse interna precisa apenas implementar os métodos de verificação de status e atualização de status , que controlarão as operações de aquisição e liberação de FutureTask. Especificamente, o Sync implementa tryAcquireShared(int)os métodos e tryReleaseShared(int)métodos do AQS, e o Sync usa esses dois métodos para verificar e atualizar o status da sincronização.

O diagrama de design do FutureTask é mostrado na figura:

imagem-20220204102958563

Como pode ser visto na figura: Sync é uma classe privada interna de FutureTask, que herda de AQS. Ao criar um FutureTask, um Sync de objeto membro privado interno é criado e todos os métodos públicos do FutureTask são delegados diretamente ao Sync privado interno

Em seguida, vamos ver o processo de execução de vários métodos.

FutureTask.get()O método chamará AQS.acquireSharedInterruptibly(int arg)o método, o processo específico é o seguinte:

  1. Chame o método AQS.acquireSharedInterruptably(int arg) Esse método primeiro chamará de volta o método tryAcquireShared() implementado na subclasse Sync para determinar se a operação de aquisição pode ser bem-sucedida.

    A condição para que a operação de aquisição seja bem-sucedida é: o estado é o estado de conclusão da execução RAN ou o estado cancelado CANCELLED e o executor não é nulo.

  2. O método get() retorna imediatamente se for bem-sucedido. Se falhar, vá para a fila de espera do encadeamento para aguardar que outros encadeamentos executem a operação de liberação.

  3. Quando outros threads executam a operação de liberação (como FutureTask.run() ou FutureTask.cancel()) para ativar o thread atual, o thread atual executa tryAcquireShared() novamente e retorna um valor positivo de 1, e o thread atual deixa a fila de espera do thread e ativa seu Thread sucessor (aqui produzirá o efeito de ativação em cascata, que será introduzido posteriormente)

  4. Por fim, retorne o resultado calculado ou lance uma exceção.

FutureTask.run()O processo de execução é o seguinte:

  1. Executa a tarefa especificada no construtor (Callable.call()).
  2. Atualize o estado de sincronização de forma atômica (chame AQS.compareAndSetState(int expect, int update), defina o estado para o estado de conclusão da execução RAN). Se a operação atômica for bem-sucedida, defina o valor da variável result que representa o resultado do cálculo para o valor de retorno de Callable.call() e, em seguida, chame AQS.releaseShared(int arg).
  3. AQS.releaseShared(int arg) primeiro chamará de volta o tryReleaseShared(arg) implementado na subclasse Sync para executar a operação de liberação (defina o executor de thread executando a tarefa como nulo e, em seguida, retorne true); AQS.releaseShared(int arg) e, em seguida, ative o encadeamento aguardando o primeiro encadeamento na fila.
  4. Chame FutureTask.done().

Quando o método FutureTask.get() é executado, se o FutureTask não estiver no estado de conclusão da execução RAN ou no estado cancelado CANCELLED, o thread de execução atual aguardará na fila de espera do AQS (consulte os threads A, B, C e D na figura abaixo). Quando um thread executa o método FutureTask.run() ou o método FutureTask.cancel(...), ele ativará o primeiro thread na fila de espera do thread (o thread E ativa o thread A mostrado na figura abaixo).

imagem-20220204105829100

Assumindo que o FutureTask não foi iniciado ou iniciado no início, já existem 3 threads (A, B e C) aguardando na fila de espera. Neste ponto, a thread D executando o método get() fará com que a thread D também espere na fila de espera.

Quando o thread E executa o método run(), ele ativa o primeiro thread A na fila. Após o encadeamento A ser ativado, ele primeiro se remove da fila, depois ativa seu encadeamento sucessor B e, finalmente, o encadeamento A retorna do método get(). Os threads B, C e D repetem o fluxo de processamento do thread A. Eventualmente, todos os threads esperando na fila são ativados pela cascata e retornam do método get().
insira a descrição da imagem aqui

Acho que você gosta

Origin blog.csdn.net/qq_45966440/article/details/122782361
Recomendado
Clasificación