Padrões e Estruturas de Concorrência em Java

Padrão Singleton

O padrão Singleton assegura que uma classe possua apenas uma instância, fornecendo um ponto de acesso global. Para implementá-lo, o construtor da classe deve ser privado, impedindo a instanciação externa, e um método estático é responsável por retornar a instância única.

Existem duas abordagens principais para sua implementação:

  • Instanciação Antecipada (Eager): O objeto é criado no momento do carregamento da classe. É inerentemente thread-safe, pois a JVM garante a segurança durante o class loading.
  • Instanciação Tardia (Lazy): O objeto é criado apenas na primeira requisição. Para ambientes multithread, exige sincronização para evitar a criação de múltiplas instâncias.

A implementação Lazy thread-safe utiliza o padrão Double-Checked Locking e a palavra-chave volatile. O volatile previne reordenação de instruções pelo compilador e garante a visibilidade da memória entre threads. O primeiro if evita o custo de aquisição de lock após a instância ser criada, enquanto o segundo if previne a criação duplicada quando múltiplas threads tentam adquirir o monitor simultaneamente.

public class RecursoUnico {
    private static volatile RecursoUnico instancia = null;

    private RecursoUnico() {}

    public static RecursoUnico obterInstancia() {
        if (instancia == null) {
            synchronized (RecursoUnico.class) {
                if (instancia == null) {
                    instancia = new RecursoUnico();
                }
            }
        }
        return instancia;
    }
}

Fila Bloqueante e o Modelo Produtor-Consumidor

Uma fila bloqueante é uma estrutura de dados thread-safe que suspende a thread produtora quando a fila está cheia e a thread consumidora quando a fila está vazia. Esse comportamento é fundamental para o modelo Produtor-Consumidor, que desacopla a geração e o processamento de dados, permitindo o controle de carga (achatar picos de requisições) em sistemas distribuídos.

Abaixo, uma implementação personalizada de uma fila circular bloqueante utilizando os métodos wait() e notifyAll() para coordenação entre threads.

public class FilaConcorrente {
    private final Object[] elementos;
    private int indiceInicio = 0;
    private int indiceFim = 0;
    private int contador = 0;

    public FilaConcorrente(int capacidade) {
        elementos = new Object[capacidade];
    }

    public synchronized void inserir(Object item) throws InterruptedException {
        while (contador == elementos.length) {
            wait();
        }
        elementos[indiceFim] = item;
        indiceFim = (indiceFim + 1) % elementos.length;
        contador++;
        notifyAll();
    }

    public synchronized Object remover() throws InterruptedException {
        while (contador == 0) {
            wait();
        }
        Object item = elementos[indiceInicio];
        indiceInicio = (indiceInicio + 1) % elementos.length;
        contador--;
        notifyAll();
        return item;
    }
}

Agendador de Tarefas

Um agandador permite executar tarefas após um determinado atraso. Internamenet, ele mantém uma fila de prioridade ordenada pelo tempo de execução e utiliza uma thread trabalhadora que monitora o topo da fila. Se o tempo da tarefa ainda não foi atingido, a thread entra em estado de espera pelo tempo restante, liberando a CPU para outros processos.

import java.util.PriorityQueue;

public class TarefaAgendada implements Comparable<TarefaAgendada> {
    private final Runnable acao;
    private final long tempoExecucao;

    public TarefaAgendada(Runnable acao, long atraso) {
        this.acao = acao;
        this.tempoExecucao = System.currentTimeMillis() + atraso;
    }

    @Override
    public int compareTo(TarefaAgendada outra) {
        return Long.compare(this.tempoExecucao, outra.tempoExecucao);
    }
    
    public long getTempoExecucao() { return tempoExecucao; }
    public Runnable getAcao() { return acao; }
}

public class AgendadorTarefas {
    private final PriorityQueue<TarefaAgendada> fila = new PriorityQueue<>();
    private final Object monitor = new Object();

    public AgendadorTarefas() {
        Thread worker = new Thread(() -> {
            while (true) {
                try {
                    synchronized (monitor) {
                        while (fila.isEmpty()) {
                            monitor.wait();
                        }
                        TarefaAgendada proxima = fila.peek();
                        long agora = System.currentTimeMillis();
                        if (agora >= proxima.getTempoExecucao()) {
                            proxima.getAcao().run();
                            fila.poll();
                        } else {
                            monitor.wait(proxima.getTempoExecucao() - agora);
                        }
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        });
        worker.setDaemon(true);
        worker.start();
    }

    public void agendar(Runnable acao, long atraso) {
        synchronized (monitor) {
            fila.offer(new TarefaAgendada(acao, atraso));
            monitor.notify();
        }
    }
}

Pool de Threads

A criação e destruição frequente de threads gera alta sobrecarga para o sistema operacional. Um pool de threads mitiga esse problema mantendo um conjunto de threads pré-criadas que aguardam tarefas em uma fila compartilhada, promovendo a reutilização de recursos e melhorando a responsividade da aplicação.

Abaixo, uma implemetnação simplificada de um pool de threads utilizando uma BlockingQueue da biblioteca padrão para gerenciar as tarefas pendentes.

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class PoolExecucao {
    private final BlockingQueue<Runnable> tarefas;

    public PoolExecucao(int tamanhoPool) {
        tarefas = new LinkedBlockingQueue<>();
        for (int i = 0; i < tamanhoPool; i++) {
            new Thread(() -> {
                while (true) {
                    try {
                        Runnable tarefa = tarefas.take();
                        tarefa.run();
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        break;
                    }
                }
            }).start();
        }
    }

    public void submeter(Runnable tarefa) {
        tarefas.offer(tarefa);
    }
}

Tags: java Concorrência singleton blocking-queue thread-pool

Publicado em 6-18 04:11