Index

Case Study: Event-Driven Programming with Queues

Java Collections

18.1 Using Queues for Event Handling

Event-driven programming is a paradigm where the flow of the program is determined by events—such as user actions, sensor inputs, or messages from other systems. In this model, queues are essential as buffers that temporarily store events before they are processed.

The Role of Queues in Event Systems

An event queue decouples the generation of events from their processing. Events are enqueued as they occur and later dequeued for handling, usually in a First-In-First-Out (FIFO) manner. This separation allows for flexible, asynchronous event handling and helps prevent missed or lost events in high-throughput systems.

How It Works:

  1. Producer: Emits or detects events (e.g., button clicks, network data).
  2. Queue: Temporarily stores these events in order.
  3. Event Loop / Dispatcher: Continuously pulls events from the queue and processes them.

Example: Simple Event Queue Processing

Below is a runnable example simulating an event-driven system using a Queue to process application events.

import java.util.*;

class Event {
    String type;
    String payload;

    Event(String type, String payload) {
        this.type = type;
        this.payload = payload;
    }

    @Override
    public String toString() {
        return "Event{" + "type='" + type + '\'' + ", payload='" + payload + '\'' + '}';
    }
}

public class EventDrivenExample {
    public static void main(String[] args) {
        Queue<Event> eventQueue = new LinkedList<>();

        // Simulate event producers
        eventQueue.offer(new Event("CLICK", "Button A"));
        eventQueue.offer(new Event("HOVER", "Image X"));
        eventQueue.offer(new Event("INPUT", "User typed 'Hello'"));

        // Event loop - simulate event consumer
        while (!eventQueue.isEmpty()) {
            Event event = eventQueue.poll(); // Fetch next event
            handleEvent(event);
        }
    }

    static void handleEvent(Event event) {
        // Event dispatch based on type
        switch (event.type) {
            case "CLICK" -> System.out.println("Handling click on: " + event.payload);
            case "HOVER" -> System.out.println("Handling hover over: " + event.payload);
            case "INPUT" -> System.out.println("Processing input: " + event.payload);
            default -> System.out.println("Unknown event type: " + event);
        }
    }
}

Expected Output

Handling click on: Button A
Handling hover over: Image X
Processing input: User typed 'Hello'

Real-World Applications

In more advanced systems, event queues might be backed by thread-safe queues (e.g., BlockingQueue) or integrated with reactive or asynchronous frameworks.

Summary

Using queues for event handling provides a scalable and modular structure for asynchronous programming. By decoupling event producers from consumers, the system becomes more responsive, manageable, and ready for concurrency.

Index

18.2 Priority Scheduling with PriorityQueue

Concept of Priority Scheduling

In many event-driven or task-processing systems, not all tasks are equal—some must be handled before others based on priority. Priority scheduling ensures that higher-priority tasks are processed first, improving responsiveness for critical events.

Java’s PriorityQueue is an ideal data structure for implementing such scheduling. Unlike a regular queue that processes elements in FIFO order, a PriorityQueue retrieves elements according to their priority (natural order or a custom comparator).

How PriorityQueue Works

Internally, PriorityQueue is usually implemented as a binary heap, providing efficient insertion and removal operations with average time complexity of O(log n).

Example 1: PriorityQueue with Natural Ordering

Here, tasks with integer priority values are scheduled, where smaller numbers indicate higher priority.

import java.util.PriorityQueue;

class Task implements Comparable<Task> {
    String name;
    int priority; // lower value = higher priority

    Task(String name, int priority) {
        this.name = name;
        this.priority = priority;
    }

    @Override
    public int compareTo(Task other) {
        return Integer.compare(this.priority, other.priority);
    }

    @Override
    public String toString() {
        return name + " (priority " + priority + ")";
    }
}

public class PrioritySchedulingExample {
    public static void main(String[] args) {
        PriorityQueue<Task> taskQueue = new PriorityQueue<>();

        taskQueue.offer(new Task("Low priority task", 5));
        taskQueue.offer(new Task("High priority task", 1));
        taskQueue.offer(new Task("Medium priority task", 3));

        System.out.println("Processing tasks in priority order:");
        while (!taskQueue.isEmpty()) {
            System.out.println("Executing: " + taskQueue.poll());
        }
    }
}

Output:

Processing tasks in priority order:
Executing: High priority task (priority 1)
Executing: Medium priority task (priority 3)
Executing: Low priority task (priority 5)

Example 2: PriorityQueue with Custom Comparator

Suppose tasks have string priorities like "HIGH", "MEDIUM", "LOW"; you can define a custom comparator to assign ordering.

import java.util.*;

class StringPriorityTask {
    String name;
    String priority; // "HIGH", "MEDIUM", "LOW"

    StringPriorityTask(String name, String priority) {
        this.name = name;
        this.priority = priority;
    }

    @Override
    public String toString() {
        return name + " (" + priority + ")";
    }
}

public class CustomComparatorExample {
    public static void main(String[] args) {
        // Define priority order
        Map<String, Integer> priorityMap = Map.of("HIGH", 1, "MEDIUM", 2, "LOW", 3);

        Comparator<StringPriorityTask> comparator = Comparator.comparingInt(
            task -> priorityMap.getOrDefault(task.priority, Integer.MAX_VALUE));

        PriorityQueue<StringPriorityTask> queue = new PriorityQueue<>(comparator);

        queue.offer(new StringPriorityTask("Task A", "LOW"));
        queue.offer(new StringPriorityTask("Task B", "HIGH"));
        queue.offer(new StringPriorityTask("Task C", "MEDIUM"));

        System.out.println("Processing tasks with custom priority:");
        while (!queue.isEmpty()) {
            System.out.println("Executing: " + queue.poll());
        }
    }
}

Output:

Processing tasks with custom priority:
Executing: Task B (HIGH)
Executing: Task C (MEDIUM)
Executing: Task A (LOW)

Summary

PriorityQueue empowers event-driven or task-based applications to efficiently handle work based on priority rather than arrival order. By leveraging natural ordering or custom comparators, you can tailor scheduling logic to fit diverse scenarios—from urgent system alerts to background maintenance jobs.

Use PriorityQueue whenever task prioritization is critical for system responsiveness and correctness.

Index