PriorityQueues and their usages

Overview

A priority queue is an interesting data structure in Java. The key term here is “priority”. That means the queue is ordered by priority of the elements, not the order they are added.

Let’s consider an example:

    private static void priorityQueueExample() {
        Queue<Integer> priority = new PriorityQueue<>();

        priority.add(1);
        priority.offer(0);
        priority.add(4);

        System.out.println("The queue is: " + priority);
    }

When that function is run, the output is:

As you can see, the order of elements in this queue is not the order they are added.

From the result, you might think that Priorityqueue orders the elements in the queue..

But this is a fatal misconception!

PriorityQueue doesn’t guarantee the order of elements. It guarantees the order of elements when you remove them from the queue.

Consider this example:

    private static void priorityQueueExampleString() {
        PriorityQueue<String> priority = new PriorityQueue<>(String::compareTo);

        priority.offer("Zed");
        priority.offer("Karen");
        priority.offer("Abby");

        System.out.println("The queue is: " + priority);
        
        System.out.println(priority.remove());
        System.out.println(priority.remove());
        System.out.println(priority.remove());
    }

You might expect that the line “The queue is: …” would print “Abby, Karen, Zed” in that order. However, that’s not the case.

Run the code and here is the output:

Priority queue doesn't guarantee order of elements, only the order when they are removed.

As you can see, the elements in the queue are not ordered alphabetically as you might expect. However, when we called the function remove, the order of items displayed correctly according to their alphabetic order.

Put another way, the highest priority element in the priority queue will be removed first.

You cannot add null to a priority queue.

Priority queue of a custom object

Let’s consider this simple object:

package com.datmt.java_core.collection.queue.priority;

public class People {
    private String name;

    public People(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
  
    @Override
    public String toString() {
        return "People[name=" + name + "]";
    }
}

How can we create a priority queue for this type?

You can create a function like this and it compiles just fine:

    private static void priorityQueueCustomObject() {
        PriorityQueue<People> people = new PriorityQueue<>();
        people.offer(new People("Jake"));
        people.offer(new People("Kim"));
        people.offer(new People("Lee"));

        System.out.println(people.remove());
    }

However, this code will throw ClassCastException. Why?

Since we are using a priority queue, there must be a mechanism to assign priority to the elements. The compiler in this case doesn’t know how to assign priority to a People instance.

What can we do?

Well, either

  • make the People class implement Comparable or
  • provide a Comparator in the PriorityQueue constructor.

We are going to implement both here.

Provide a Comparator

Let’s say we order people by their name.

    private static void priorityQueueCustomObjectUsingComparator() {
        PriorityQueue<People> people = new PriorityQueue<>(Comparator.comparing(People::getName));
        people.offer(new People("Aaron"));
        people.offer(new People("Zed"));
        people.offer(new People("Chris"));

        System.out.println(people.remove());
        System.out.println(people.remove());
        System.out.println(people.remove());
    }

This is the output, as you might expect:

The benefit of this method is you don’t need to modify the People class. We only need to provide a Comparator in the queue’s constructor.

Implement the Comparable interface

Instead of providing a Comparator, we can solve the issue by making the People class implement the Comparable interface.

The People class now looks like this:

package com.datmt.java_core.collection.queue.priority;

public class People implements Comparable<People> {
    private String name;

    public People(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "People[name=" + name + "]";
    }

    @Override
    public int compareTo(People people) {
        return name.compareTo(people.getName());
    }
}

Then, we can implement a priority queue of people without providing a comparator:

    private static void priorityQueueCustomObjectUsingComparableInterface() {
        PriorityQueue<People> people = new PriorityQueue<>();
        people.offer(new People("Aaron"));
        people.offer(new People("Zed"));
        people.offer(new People("Chris"));

        System.out.println(people.remove());
        System.out.println(people.remove());
        System.out.println(people.remove());
    }

This approach works just like the other.

Using priority queue with Concurrent code

If your queue is accessed, or modified by multiple threads, PriorityQueue is not a good option. Instead, you would consider the thread-safe implementation of the priority queue PriorityBlockingQueue

Let’s consider this concurrent code that use PriorityQueue and see the problem:

    private static void nonSafePriorityQueue() throws InterruptedException {
        PriorityQueue<Integer> queue = new PriorityQueue<>();
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        //adding 20 elements
        for (int i = 0; i < 20; i ++) {
            int finalI = i;
            executorService.submit(() -> queue.offer(finalI));
        }

        executorService.shutdown();
        executorService.awaitTermination(10, TimeUnit.SECONDS);

        System.out.println("queue: " + queue + " has " + queue.size() + " items");
    }

When you run this code, the final queue sometimes has 17 items, sometimes has 18 items…

You may have a totally different result.

Here are the results after some executions:

Non thread safe code using priority queue

Now, let’s switch to the same code but use PriorityBlockingQueue

    private static void safePriorityQueue() throws InterruptedException {
        PriorityBlockingQueue<Integer> queue = new PriorityBlockingQueue<>();
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        //adding 20 elements
        for (int i = 0; i < 20; i ++) {
            int finalI = i;
            executorService.submit(() -> queue.offer(finalI));
        }

        executorService.shutdown();
        executorService.awaitTermination(10, TimeUnit.SECONDS);

        System.out.println("queue: " + queue + " has " + queue.size() + " items");
    }

The order of items in queue could be different between executions, however, the queue always has 20 items from 0 to 19 as you expect:

Thread safe blocking queue

Similar to PriorityQueue, you cannot add null to PriorityBlockingQueue

Conclusion

In this post, we discussed the use of PriorityQueue, how to add custom objects to a queue and how can we handle the queue in multi-threaded code.

The code of this post is available here on Github.


Leave a Reply

Your email address will not be published.