Planification des tâches basée sur les priorités en Java

Planification des tâches basée sur les priorités en Java

1. introduction

Dans un environnement multithread, il est parfois nécessaire de planifier des tâches en fonction de critères personnalisés au lieu de la date de création.

Voyons comment nous pouvons y parvenir en Java - en utilisant unPriorityBlockingQueue.

2. Vue d'ensemble

Disons que nous souhaitons exécuter des tâches en fonction de leur priorité:

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
}

À des fins de démonstration, nous imprimons le nom et la priorité du travail dans la méthoderun().

Nous avons également ajoutésleep() afin de simuler un travail plus long; pendant l'exécution du travail, davantage de travaux seront accumulés dans la file d'attente prioritaire.

Enfin,JobPriority est une simple énumération:

public enum JobPriority {
    HIGH,
    MEDIUM,
    LOW
}

3. PersonnaliséComparator

Nous devons rédiger un comparateur définissant nos critères personnalisés; et, enJava 8, it’s trivial:

Comparator.comparing(Job::getJobPriority);

4. Planificateur de travaux prioritaires

Une fois la configuration terminée, implémentons maintenant un simple planificateur de tâches - qui utilise un exécuteur de thread unique pour rechercher les tâches dans lesPriorityBlockingQueue et les exécuter:

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. Le prochain travail à exécuter est sélectionné dans la file d'attente à l'aide de la méthodetake() qui récupère et supprime la tête de la file d'attente.

Le code client a maintenant simplement besoin d'appeler lescheduleJob() - ce qui ajoute le travail à la file d'attente. LepriorityQueue.add() met le travail en file d'attente à la position appropriée par rapport aux travaux existants dans la file d'attente, en utilisant lesJobExecutionComparator.

Notez que les travaux réels sont exécutés en utilisant unExecutorService séparé avec un pool de threads dédié.

5. Demo

Enfin, voici une rapide démonstration du planificateur:

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
}

Afin de démontrer que les travaux sont exécutés dans l'ordre de priorité, nous avons conservé lesPOOL_SIZE à 1 même si leQUEUE_SIZE est égal à 10. Nous fournissons des travaux avec une priorité variable au planificateur.

Voici un exemple de sortie obtenu pour l'une des exécutions:

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

La sortie peut varier d'une piste à l'autre. Cependant, nous ne devrions jamais avoir un cas dans lequel un travail de priorité inférieure est exécuté, même si la file d'attente contient un travail de priorité supérieure.

6. Conclusion

Dans ce rapide tutoriel, nous avons vu commentPriorityBlockingQueue peut être utilisé pour exécuter des tâches dans un ordre de priorité personnalisé.

Comme d'habitude, les fichiers source peuvent être trouvésover on GitHub.