Planejamento de tarefas baseado em prioridade em Java

Planejamento de tarefas baseado em prioridade em Java

1. Introdução

Em um ambiente multithread, às vezes precisamos agendar tarefas com base em critérios personalizados, em vez de apenas na hora da criação.

Vamos ver como podemos fazer isso em Java - usando umPriorityBlockingQueue.

2. Visão geral

Digamos que temos trabalhos que queremos executar com base em sua prioridade:

public class Job implements Runnable {
    private String jobName;
    private JobPriority jobPriority;

    @Override
    public void run() {
        System.out.println("Job:" + jobName +
          " Priority:" + jobPriority);
        Thread.sleep(1000); // to simulate actual execution time
    }

    // standard setters and getters
}

Para fins de demonstração, estamos imprimindo o nome do trabalho e a prioridade no métodorun().

Também adicionamossleep() para simular um trabalho de execução mais longa; enquanto o trabalho está sendo executado, mais trabalhos serão acumulados na fila de prioridade.

Finalmente,JobPriority é um enum simples:

public enum JobPriority {
    HIGH,
    MEDIUM,
    LOW
}

3. Comparator personalizado

Precisamos escrever um comparador definindo nossos critérios personalizados; e, emJava 8, it’s trivial:

Comparator.comparing(Job::getJobPriority);

4. Priority Job Scheduler

Com toda a configuração feita, vamos agora implementar um agendador de tarefas simples - que emprega um único executor de thread para procurar tarefas emPriorityBlockingQueuee as executa:

public class PriorityJobScheduler {

    private ExecutorService priorityJobPoolExecutor;
    private ExecutorService priorityJobScheduler
      = Executors.newSingleThreadExecutor();
    private PriorityBlockingQueue priorityQueue;

    public PriorityJobScheduler(Integer poolSize, Integer queueSize) {
        priorityJobPoolExecutor = Executors.newFixedThreadPool(poolSize);
        priorityQueue = new PriorityBlockingQueue(
          queueSize,
          Comparator.comparing(Job::getJobPriority));
        priorityJobScheduler.execute(() -> {
            while (true) {
                try {
                    priorityJobPoolExecutor.execute(priorityQueue.take());
                } catch (InterruptedException e) {
                    // exception needs special handling
                    break;
                }
            }
        });
    }

    public void scheduleJob(Job job) {
        priorityQueue.add(job);
    }
}

The key here is to create an instance of PriorityBlockingQueue of Job type with a custom comparator. O próximo trabalho a ser executado é selecionado da fila usando o métodotake(), que recupera e remove o início da fila.

O código do cliente agora precisa apenas chamarscheduleJob() - que adiciona o trabalho à fila. OpriorityQueue.add() enfileira o trabalho na posição apropriada em comparação com os trabalhos existentes na fila, usando oJobExecutionComparator.

Observe que os trabalhos reais são executados usando umExecutorService separado com um pool de threads dedicado.

5. Demo

Finalmente, aqui está uma rápida demonstração do agendador:

private static int POOL_SIZE = 1;
private static int QUEUE_SIZE = 10;

@Test
public void whenMultiplePriorityJobsQueued_thenHighestPriorityJobIsPicked() {
    Job job1 = new Job("Job1", JobPriority.LOW);
    Job job2 = new Job("Job2", JobPriority.MEDIUM);
    Job job3 = new Job("Job3", JobPriority.HIGH);
    Job job4 = new Job("Job4", JobPriority.MEDIUM);
    Job job5 = new Job("Job5", JobPriority.LOW);
    Job job6 = new Job("Job6", JobPriority.HIGH);

    PriorityJobScheduler pjs = new PriorityJobScheduler(
      POOL_SIZE, QUEUE_SIZE);

    pjs.scheduleJob(job1);
    pjs.scheduleJob(job2);
    pjs.scheduleJob(job3);
    pjs.scheduleJob(job4);
    pjs.scheduleJob(job5);
    pjs.scheduleJob(job6);

    // clean up
}

Para demonstrar que as tarefas são executadas em ordem de prioridade, mantivemosPOOL_SIZE como 1, emboraQUEUE_SIZE seja 10. Fornecemos trabalhos com prioridade variável ao agendador.

Aqui está um exemplo de saída que obtivemos para uma das execuções:

Job:Job3 Priority:HIGH
Job:Job6 Priority:HIGH
Job:Job4 Priority:MEDIUM
Job:Job2 Priority:MEDIUM
Job:Job1 Priority:LOW
Job:Job5 Priority:LOW

A saída pode variar entre as execuções. No entanto, nunca devemos ter um caso em que um trabalho de prioridade mais baixa seja executado, mesmo quando a fila contiver um trabalho de prioridade mais alta.

6. Conclusão

Neste tutorial rápido, vimos comoPriorityBlockingQueue pode ser usado para executar tarefas em uma ordem de prioridade personalizada.

Como de costume, os arquivos de origem podem ser encontradosover on GitHub.