Skip to content

Latest commit

 

History

History

leader-followers

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
title shortTitle description category language tag
Leader-Followers Pattern in Java: Enhancing Efficiency with Dynamic Worker Allocation
Leader-Followers
Discover the Leader-Followers design pattern for efficient thread management and synchronization. Learn how to optimize resource usage and improve system performance with detailed examples and applications.
Concurrency
en
Decoupling
Performance
Synchronization
Thread management

Intent of Leader/Followers Design Pattern

To efficiently manage a set of worker threads, the Leader-Followers pattern enables multiple threads to take turns sharing a set of event sources, optimizing resource utilization and improving performance compared to a one-thread-per-source approach.

Detailed Explanation of Leader/Followers Pattern with Real-World Examples

Real-world Example

Imagine managing a busy restaurant with multiple servers and a single host. The host acts as the "leader" and is responsible for greeting guests, managing the waitlist, and seating guests. Once the guests are seated, the host returns to the entrance to manage new arrivals. The servers, or "followers," wait for the host to assign them tables. This assignment is done on a rotational basis where the next available server takes the next group of guests. This system ensures that the host efficiently handles the incoming flow of guests while servers focus on providing service, similar to how the Leader and Followers pattern manages threads and tasks in a software system. This approach optimizes resource utilization (in this case, staff) and ensures smooth operations during peak times, much like it optimizes thread usage in computing environments.

In plain words

The Leader-Followers design pattern utilizes a single leader thread to distribute work among multiple follower threads, effectively managing task delegation, thread synchronization, and improving resource efficiency in concurrent programming.

martinfowler.com says

Select one server in the cluster as a leader. The leader is responsible for taking decisions on behalf of the entire cluster and propagating the decisions to all the other servers.

Programmatic Example of Leader-Followers Pattern in Java

The Leader-Followers pattern is a concurrency design pattern where one thread (the leader) waits for work to arrive, de-multiplexes, dispatches, and processes the work, thereby enhancing CPU cache affinity and reducing event dispatching latency. Once the leader finishes processing the work, it promotes one of the follower threads to be the new leader. This pattern is useful for enhancing CPU cache affinity, minimizing locking overhead, and reducing event dispatching latency.

In the provided code, we have a WorkCenter class that manages a group of Worker threads. One of these workers is designated as the leader and is responsible for receiving and processing tasks. Once a task is processed, the leader promotes a new leader from the remaining workers.

// WorkCenter class
public class WorkCenter {

  @Getter
  private Worker leader;
  private final List<Worker> workers = new CopyOnWriteArrayList<>();

  // Method to create workers and set the initial leader
  public void createWorkers(int numberOfWorkers, TaskSet taskSet, TaskHandler taskHandler) {
    for (var id = 1; id <= numberOfWorkers; id++) {
      var worker = new Worker(id, this, taskSet, taskHandler);
      workers.add(worker);
    }
    promoteLeader();
  }

  // Method to promote a new leader
  public void promoteLeader() {
    Worker leader = null;
    if (!workers.isEmpty()) {
      leader = workers.get(0);
    }
    this.leader = leader;
  }
}

In the Worker class, each worker is a thread that waits for tasks to process. If the worker is the leader, it processes the task and then promotes a new leader.

// Worker class
public class Worker implements Runnable {

  private final long id;
  private final WorkCenter workCenter;
  private final TaskSet taskSet;
  private final TaskHandler taskHandler;

  @Override
  public void run() {
    while (!Thread.interrupted()) {
      try {
        if (workCenter.getLeader() != null && !workCenter.getLeader().equals(this)) {
          synchronized (workCenter) {
            if (workCenter.getLeader() != null && !workCenter.getLeader().equals(this)) {
              workCenter.wait();
              continue;
            }
          }
        }
        final Task task = taskSet.getTask();
        synchronized (workCenter) {
          workCenter.removeWorker(this);
          workCenter.promoteLeader();
          workCenter.notifyAll();
        }
        taskHandler.handleTask(task);
        workCenter.addWorker(this);
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        return;
      }
    }
  }
}

In the App class, we create a WorkCenter, add tasks to a TaskSet, and then start the workers. The leader worker will start processing the tasks, and once it finishes a task, it will promote a new leader.

// App class
public class App {

  public static void main(String[] args) throws InterruptedException {
    var taskSet = new TaskSet();
    var taskHandler = new TaskHandler();
    var workCenter = new WorkCenter();
    workCenter.createWorkers(4, taskSet, taskHandler);
    addTasks(taskSet);
    startWorkers(workCenter);
  }

  private static void addTasks(TaskSet taskSet) throws InterruptedException {
    var rand = new SecureRandom();
    for (var i = 0; i < 5; i++) {
      var time = Math.abs(rand.nextInt(1000));
      taskSet.addTask(new Task(time));
    }
  }

  private static void startWorkers(WorkCenter workCenter) throws InterruptedException {
    var workers = workCenter.getWorkers();
    var exec = Executors.newFixedThreadPool(workers.size());
    workers.forEach(exec::submit);
    exec.awaitTermination(2, TimeUnit.SECONDS);
    exec.shutdownNow();
  }
}

This is a basic example of the Leader/Followers pattern. The leader worker processes tasks and promotes a new leader once it finishes a task. The new leader then starts processing the next task, and the cycle continues.

When to Use the Leader/Followers Pattern in Java

  • The Leader-Followers pattern is useful in scenarios requiring efficient handling of multiple services on a single thread, avoiding resource thrashing, and improving scalability in concurrent programming environments.
  • Applicable in server environments where multiple client requests must be handled concurrently with minimal resource consumption.

Real-World Applications of Leader-Followers Pattern in Java

  • Network servers handling multiple incoming connections.
  • Event-driven applications that manage a large number of input/output sources.

Benefits and Trade-offs of Leader/Followers Pattern

Benefits:

  • Reduces the number of threads and context switching, leading to better performance and lower resource utilization.
  • Improves system scalability and responsiveness.

Trade-offs:

  • Increased complexity in managing the synchronization between leader and followers.
  • Potential for underutilization of resources if not correctly implemented.

Related Java Design Patterns

  • Half-Sync/Half-Async: Leader and Followers can be seen as a variation where the synchronization aspect is divided between the leader (synchronous handling) and followers (waiting asynchronously).
  • Thread Pool: Both patterns manage a pool of worker threads, but Thread Pool assigns tasks to any available thread rather than using a leader to distribute work.

References and Credits