Implementando Fila Bloqueante em Java para Processamento Paralelo de Arquivos

Arquitetura da Solução

O sistema é composto por três elementos principais:

  • FileScannerApp: classe principal que orquestra a execução
  • FileCrawler: responsável por varrer diretórios e alimentar a fila
  • ContentMatcher: consome arquivos da fila e verifica padrões

Classe Principal

package br.exemplo.filabloqueante;

import java.io.File;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class FileScannerApp {
    public static void main(String[] args) {
        String caminhoDiretorio = "e://testfile//";
        String termoBusca = "aaa";
        
        final int CAPACIDADE_FILA = 5;
        final int QUANTIDADE_CONSUMIDORES = 10;
        
        BlockingQueue<File> filaArquivos = new ArrayBlockingQueue<>(CAPACIDADE_FILA);
        
        Thread produtor = new Thread(new FileCrawler(filaArquivos, new File(caminhoDiretorio)));
        produtor.start();
        
        for (int i = 0; i < QUANTIDADE_CONSUMIDORES; i++) {
            System.out.println("Iniciando consumidor " + (i + 1));
            new Thread(new ContentMatcher(filaArquivos, termoBusca)).start();
        }
    }
}

Produtor: Varredura de Diretórios

package br.exemplo.filabloqueante;

import java.io.File;
import java.util.concurrent.BlockingQueue;

class FileCrawler implements Runnable {
    
    public static final File SENTINELA = new File("");
    private final BlockingQueue<File> fila;
    private final File diretorioRaiz;

    public FileCrawler(BlockingQueue<File> fila, File diretorioRaiz) {
        this.fila = fila;
        this.diretorioRaiz = diretorioRaiz;
    }

    @Override
    public void run() {
        try {
            explorar(diretorioRaiz);
            fila.put(SENTINELA);
        } catch (InterruptedException ex) {
            Thread.currentThread().interrupt();
        }
    }

    private void explorar(File diretorio) throws InterruptedException {
        File[] conteudo = diretorio.listFiles();
        if (conteudo == null) return;
        
        for (File item : conteudo) {
            if (item.isDirectory()) {
                explorar(item);
            } else {
                fila.put(item);
            }
        }
    }
}

Consumidor: Análise de Conteúdo

package br.exemplo.filabloqueante;

import java.io.File;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.BlockingQueue;

public class ContentMatcher implements Runnable {

    private final BlockingQueue<File> fila;
    private final String padrao;

    public ContentMatcher(BlockingQueue<File> fila, String padrao) {
        this.fila = fila;
        this.padrao = padrao;
    }

    @Override
    public void run() {
        try {
            boolean finalizado = false;
            while (!finalizado) {
                File arquivo = fila.take();
                
                if (arquivo == FileCrawler.SENTINELA) {
                    fila.put(arquivo);
                    finalizado = true;
                } else {
                    analisar(arquivo);
                }
            }
        } catch (IOException | InterruptedException ex) {
            ex.printStackTrace();
        }
    }

    private void analisar(File arquivo) throws IOException {
        try (Scanner leitor = new Scanner(arquivo)) {
            int numeroLinha = 0;
            System.out.println("Analisando: " + arquivo.getName());
            
            while (leitor.hasNextLine()) {
                numeroLinha++;
                String linha = leitor.nextLine();
                
                if (linha.contains(padrao)) {
                    System.out.printf("%s [%d]: %s%n", 
                        arquivo.getAbsolutePath(), 
                        numeroLinha, 
                        linha
                    );
                }
            }
        }
    }
}

Observações Importantes

A estratégia de sentinelas permite que todos os consumidores identifiquem o término do processamento. Ao encontrar o marcador, cada consumidor o recoloca na fila para que os demais também possam encerrar.

A capacidade limitada da ArrayBlockingQueue impede que o produtor acumule muitos arquivos em memória, controlando o uso de recursos durante a varredura de hierarquias extensas.

Tags: java BlockingQueue Multithreading Concurrency ArrayBlockingQueue

Publicado em 9-3 03:01