Руководство по PriorityBlockingQueue в Java

Руководство по PriorityBlockingQueue в Java

1. Вступление

В этой статье мы сосредоточимся на классеPriorityBlockingQueue и рассмотрим несколько практических примеров.

Исходя из предположения, что мы уже знаем, что такоеQueue, сначала продемонстрируемhow elements in the PriorityBlockingQueue are ordered by priority.

После этого мы продемонстрируем, как этот тип очереди может использоваться для блокировки потока.

Наконец, мы покажем, как совместное использование этих двух функций может быть полезным при обработке данных в нескольких потоках.

2. Приоритет элементов

В отличие от стандартной очереди, вы не можете просто добавить любой тип элемента вPriorityBlockingQueue.. Есть два варианта:

  1. Добавление элементов, реализующихComparable

  2. Добавление элементов, которые не реализуютComparable, при условии, что вы также предоставитеComparator

При использовании реализацийComparator илиComparable для сравнения элементов,PriorityBlockingQueue всегда будет отсортирован.

Цель состоит в том, чтобы реализовать логику сравнения таким образом, чтобыthe highest priority element is always ordered first. Затем, когда мы удаляем элемент из нашей очереди, он всегда будет иметь самый высокий приоритет.

Для начала давайте использовать нашу очередь в одном потоке, а не в нескольких. Это позволяет легко доказать, как элементы упорядочены в модульном тесте:

PriorityBlockingQueue queue = new PriorityBlockingQueue<>();
ArrayList polledElements = new ArrayList<>();

queue.add(1);
queue.add(5);
queue.add(2);
queue.add(3);
queue.add(4);

queue.drainTo(polledElements);

assertThat(polledElements).containsExactly(1, 2, 3, 4, 5);

Как мы видим, несмотря на добавление элементов в очередь в случайном порядке, они будут упорядочены, когда мы начнем опрашивать их. Это связано с тем, что классInteger реализуетComparable,, который, в свою очередь, будет использоваться, чтобы убедиться, что мы извлекаем их из очереди в порядке возрастания.

Также стоит отметить, чтоwhen two elements are compared and are the same, there’s no guarantee of how they will be ordered.

3. ИспользованиеQueue для блокировки

Если бы мы имели дело со стандартной очередью, мы бы вызывалиpoll() для извлечения элементов. Однако, если очередь была пустой, вызовpoll() вернул быnull.

PriorityBlockingQueue реализует интерфейсBlockingQueue, который дает нам несколько дополнительных методов, позволяющих использоватьblock when removing from an empty queue. Давайте попробуем использовать методtake(), который должен делать именно это:

PriorityBlockingQueue queue = new PriorityBlockingQueue<>();

new Thread(() -> {
  System.out.println("Polling...");

  try {
      Integer poll = queue.take();
      System.out.println("Polled: " + poll);
  } catch (InterruptedException e) {
      e.printStackTrace();
  }
}).start();

Thread.sleep(TimeUnit.SECONDS.toMillis(5));
System.out.println("Adding to queue");
queue.add(1);

Хотя использованиеsleep() является немного хрупким способом демонстрации вещей, когда мы запустим этот код, мы увидим:

Polling...
Adding to queue
Polled: 1

Это доказывает, чтоtake() заблокирован, пока элемент не был добавлен:

  1. Цепочка напечатает "Опрос", чтобы подтвердить, что он начался.

  2. Затем тест будет приостановлен примерно на пять секунд, чтобы доказать, что к этому моменту поток должен был вызватьtake().

  3. Мы добавляем в очередь и более или менее сразу должны увидеть «Polled: 1», чтобы доказать, чтоtake() вернул элемент, как только он станет доступным.

Также стоит упомянуть, что интерфейсBlockingQueue также предоставляет нам способы блокировки при добавлении в полные очереди.

Однако aPriorityBlockingQueue неограничен. Это означает, что он никогда не будет полным, поэтому всегда можно добавлять новые элементы.

4. Совместное использование блокировки и приоритезации

Теперь, когда мы объяснили две ключевые концепции aPriorityBlockingQueue,, давайте использовать их оба вместе. Мы можем просто расширить наш предыдущий пример, но на этот раз добавим больше элементов в очередь:

Thread thread = new Thread(() -> {
    System.out.println("Polling...");
    while (true) {
        try {
            Integer poll = queue.take();
            System.out.println("Polled: " + poll);
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
});

thread.start();

Thread.sleep(TimeUnit.SECONDS.toMillis(5));
System.out.println("Adding to queue");

queue.addAll(newArrayList(1, 5, 6, 1, 2, 6, 7));
Thread.sleep(TimeUnit.SECONDS.toMillis(1));

Опять же, хотя это немного хрупко из-за использованияsleep(),, оно все же показывает нам допустимый вариант использования. Теперь у нас есть очередь, которая блокирует, ожидая добавления элементов. Затем мы добавляем сразу несколько элементов и показываем, что они будут обрабатываться в приоритетном порядке. Вывод будет выглядеть так:

Polling...
Adding to queue
Polled: 1
Polled: 1
Polled: 2
Polled: 5
Polled: 6
Polled: 6
Polled: 7

5. Заключение

В этом руководстве мы продемонстрировали, как мы можем использоватьPriorityBlockingQueue, чтобы заблокировать поток до тех пор, пока в него не будут добавлены некоторые элементы, а также то, что мы можем обрабатывать эти элементы в зависимости от их приоритета.

Реализацию этих примеров можно найти вover on GitHub. Это проект, основанный на Maven, поэтому его легко запустить как есть.