Guia para o Java Phaser

Guia para o Java Phaser

*1. Visão geral *

Neste artigo, veremos a construção https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Phaser.html [Phaser] _ do pacote _java.util.concurrent . É uma construção muito semelhante ao link:/java-countdown-latch [CountDownLatch] que nos permite coordenar a execução de threads. Em comparação com o CountDownLatch, ele possui algumas funcionalidades adicionais.

O Phaser é uma barreira na qual o número dinâmico de threads precisa aguardar antes de continuar a execução. No CountDownLatch, esse número não pode ser configurado dinamicamente e precisa ser fornecido quando estamos criando a instância.

===* 2. API Phaser *

O Phaser nos permite construir lógica na qual* threads precisam esperar na barreira antes de ir para o próximo passo da execução *.

Podemos coordenar várias fases de execução, reutilizando uma instância Phaser para cada fase do programa. Cada fase pode ter um número diferente de threads aguardando o avanço para outra fase. Vamos dar uma olhada em um exemplo de uso de fases posteriormente.

Para participar da coordenação, o encadeamento precisa register () _ próprio com a instância _Phaser. Observe que isso apenas aumenta o número de partes registradas e não podemos verificar se o encadeamento atual está registrado - teríamos que subclassificar a implementação para suportar isso.

O encadeamento sinaliza que chegou à barreira chamando _arriveAndAwaitAdvance () _, que é um método de bloqueio. Quando o número de partes que chegaram é igual ao número de partes registradas, a execução do programa continuará e o número da fase aumentará. Podemos obter o número da fase atual chamando o método _getPhase () _.

Quando o encadeamento termina seu trabalho, devemos chamar o método _arriveAndDeregister () _ para sinalizar que o encadeamento atual não deve mais ser considerado nessa fase específica.

*3. Implementando a lógica usando a API Phaser *

Digamos que queremos coordenar várias fases de ações. Três threads irão processar a primeira fase e dois threads irão processar a segunda fase.

Criaremos uma classe LongRunningAction que implementa a interface Runnable:

class LongRunningAction implements Runnable {
    private String threadName;
    private Phaser ph;

    LongRunningAction(String threadName, Phaser ph) {
        this.threadName = threadName;
        this.ph = ph;
        ph.register();
    }

    @Override
    public void run() {
        ph.arriveAndAwaitAdvance();
        try {
            Thread.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        ph.arriveAndDeregister();
    }
}

Quando nossa classe de ação é instanciada, estamos registrando a instância Phaser usando o método register () _. Isso aumentará o número de threads usando o _Phaser. específico

A chamada para _arriveAndAwaitAdvance () _ fará com que o encadeamento atual aguarde na barreira. Como já mencionado, quando o número de partes que chegaram chegou ao mesmo número de partes registradas, a execução continuará.

Após o término do processamento, o encadeamento atual é cancelado o registro, chamando o método _arriveAndDeregister () _.

Vamos criar um caso de teste no qual iniciaremos três threads LongRunningAction e bloquearemos a barreira. Em seguida, após a conclusão da ação, criaremos dois threads LongRunningAction adicionais que executarão o processamento da próxima fase.

Ao criar a instância Phaser a partir do thread principal, estamos passando 1 como argumento. Isso é equivalente a chamar o método register () _ do thread atual. Estamos fazendo isso porque, ao criar três threads de trabalho, o thread principal é um coordenador e, portanto, o _Phaser precisa ter quatro threads registrados:

ExecutorService executorService = Executors.newCachedThreadPool();
Phaser ph = new Phaser(1);

assertEquals(0, ph.getPhase());

A fase após a inicialização é igual a zero.

A classe Phaser possui um construtor no qual podemos passar uma instância pai para ela. É útil nos casos em que temos um grande número de partes que enfrentam enormes custos de contenção de sincronização. Em tais situações, instâncias de Phasers podem ser configuradas para que grupos de sub-fases compartilhem um pai comum.

Em seguida, vamos iniciar três threads de ação LongRunningAction, que estarão aguardando na barreira até chamarmos o método _arriveAndAwaitAdvance () _ a partir do thread principal.

Lembre-se de que inicializamos nosso Phaser com 1 e chamamos _register () _ mais três vezes. Agora, três threads de ação anunciaram que chegaram à barreira, portanto, é necessária mais uma chamada de _arriveAndAwaitAdvance () _ - a da thread principal:

executorService.submit(new LongRunningAction("thread-1", ph));
executorService.submit(new LongRunningAction("thread-2", ph));
executorService.submit(new LongRunningAction("thread-3", ph));

ph.arriveAndAwaitAdvance();

assertEquals(1, ph.getPhase());

Após a conclusão dessa fase, o método _getPhase () _ retornará um porque o programa concluiu o processamento da primeira etapa da execução.

Digamos que dois threads devem conduzir a próxima fase do processamento. Podemos aproveitar o Phaser para conseguir isso, pois nos permite configurar dinamicamente o número de threads que devem esperar na barreira. Estamos iniciando dois novos encadeamentos, mas eles não continuarão a ser executados até a chamada para _arriveAndAwaitAdvance () _ do encadeamento principal (igual ao caso anterior):

executorService.submit(new LongRunningAction("thread-4", ph));
executorService.submit(new LongRunningAction("thread-5", ph));
ph.arriveAndAwaitAdvance();

assertEquals(2, ph.getPhase());

ph.arriveAndDeregister();

Depois disso, o método getPhase () _ retornará o número da fase igual a dois. Quando queremos concluir nosso programa, precisamos chamar o método _arriveAndDeregister () _, pois o encadeamento principal ainda está registrado no _Phaser. Quando o cancelamento de registro faz com que o número de partes registradas se torne zero, o Phaser é terminado. as chamadas para métodos de sincronização não serão mais bloqueadas e retornarão imediatamente.

A execução do programa produzirá a seguinte saída (o código-fonte completo com as instruções da linha de impressão pode ser encontrado no repositório de códigos):

This is phase 0
This is phase 0
This is phase 0
Thread thread-2 before long running action
Thread thread-1 before long running action
Thread thread-3 before long running action
This is phase 1
This is phase 1
Thread thread-4 before long running action
Thread thread-5 before long running action

Vemos que todos os threads aguardam execução até que a barreira se abra. A próxima fase da execução é realizada apenas quando a anterior foi concluída com êxito.

===* 4. Conclusão*

Neste tutorial, vimos a construção Phaser de java.util.concurrent e implementamos a lógica de coordenação com várias fases usando a classe Phaser.

A implementação de todos esses exemplos e trechos de código pode ser encontrada no GitHub projeto - este é um projeto Maven, portanto, deve ser fácil importar e executar como está.