Приоритетное планирование заданий в Java
1. Вступление
В многопоточной среде иногда нам нужно планировать задачи на основе пользовательских критериев, а не только времени создания.
Давайте посмотрим, как мы можем добиться этого в Java - используяPriorityBlockingQueue.
2. обзор
Допустим, у нас есть задания, которые мы хотим выполнить в зависимости от их приоритета:
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
}
В демонстрационных целях мы печатаем название задания и приоритет в методеrun().
Мы также добавилиsleep(), чтобы смоделировать более длительное задание; пока задание выполняется, в очереди с приоритетами накапливается больше заданий.
Наконец,JobPriority - это простое перечисление:
public enum JobPriority {
HIGH,
MEDIUM,
LOW
}
3. ПользовательскийComparator
Нам нужно написать компаратор, определяющий наши пользовательские критерии; и вJava 8, it’s trivial:
Comparator.comparing(Job::getJobPriority);
4. Планировщик приоритетных заданий
После завершения настройки давайте теперь реализуем простой планировщик заданий, который использует однопоточный исполнитель для поиска заданий вPriorityBlockingQueue и их выполнения:
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. Следующее задание для выполнения выбирается из очереди с помощью методаtake(), который извлекает и удаляет заголовок очереди.
Теперь клиентскому коду просто нужно вызватьscheduleJob(), который добавляет задание в очередь. priorityQueue.add() ставит задание в очередь в соответствующей позиции по сравнению с существующими заданиями в очереди, используяJobExecutionComparator.
Обратите внимание, что фактические задания выполняются с использованием отдельногоExecutorService с выделенным пулом потоков.
5. Demo
Наконец, вот быстрая демонстрация планировщика:
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
}
Чтобы продемонстрировать, что задания выполняются в порядке приоритета, мы сохранили дляPOOL_SIZE значение 1, даже еслиQUEUE_SIZE равно 10. Мы предоставляем задания с разным приоритетом для планировщика.
Вот пример вывода, который мы получили для одного из прогонов:
Job:Job3 Priority:HIGH
Job:Job6 Priority:HIGH
Job:Job4 Priority:MEDIUM
Job:Job2 Priority:MEDIUM
Job:Job1 Priority:LOW
Job:Job5 Priority:LOW
Выход может варьироваться в зависимости от трассы. Однако у нас никогда не должно быть случая, когда задание с более низким приоритетом выполняется, даже когда очередь содержит задание с более высоким приоритетом.
6. Заключение
В этом кратком руководстве мы увидели, какPriorityBlockingQueue можно использовать для выполнения заданий в произвольном порядке приоритета.
Как обычно, исходные файлы можно найтиover on GitHub.