Active Object Pattern

The Active Object Pattern is a concurrency design pattern that decouples method invocation from method execution by using an intermediary to manage asynchronous requests. It allows clients to invoke methods on an object as if they were synchronous, while the actual execution occurs asynchronously in a separate thread. This pattern is particularly useful for improving responsiveness in systems with long-running or blocking operations, such as I/O tasks, distributed systems, or event-driven applications.

Important Components

  1. Active Object:
    • The main object that clients interact with, encapsulating the asynchronous method execution.
    • Acts as a facade, hiding the complexity of threading and task scheduling.
  2. Proxy:
    • An interface or class that clients call to invoke methods on the Active Object.
    • Converts method calls into tasks (requests) and enqueues them for execution.
    • Returns a Future or similar placeholder for asynchronous results.
  3. Request Queue:
    • A thread-safe queue that holds method invocation requests (tasks) until they can be executed.
    • Often implemented as a BlockingQueue in Java to handle producer-consumer interactions.
  4. Scheduler:
    • A component (typically a dedicated thread) that dequeues requests from the queue and dispatches them to the Servant for execution.
    • Manages the execution order (e.g., FIFO, priority-based).
  5. Servant:
    • The object that contains the actual implementation of the methods (business logic).
    • Executes the requests dequeued by the Scheduler in its own thread.
  6. Future/Promise:
    • A placeholder for the result of an asynchronous method call, allowing clients to retrieve the result later.
    • In Java, typically a Future or CompletableFuture.
  7. Client:
    • Invokes methods on the Proxy, receives Futures, and retrieves results when needed.

How It Works

  • The Client calls a method on the Proxy, which creates a request object encapsulating the method call (e.g., method name, arguments).
  • The Proxy enqueues the request in the Request Queue and immediately returns a Future to the Client.
  • The Scheduler (running in a separate thread) continuously dequeues requests from the queue.
  • The Scheduler dispatches each request to the Servant, which executes the corresponding method.
  • The Servant’s result (or exception) is stored in the Future, which the Client can query using Future.get() (blocking) or asynchronous callbacks (e.g., with CompletableFuture).
  • The pattern decouples the Client’s method invocation from the Servant’s execution, allowing asynchronous processing and improving responsiveness.

Benefits

  • Asynchronous Execution: Clients can continue work without waiting for method execution, improving responsiveness.
  • Decoupling: Separates method invocation (Proxy) from execution (Servant), enhancing modularity.
  • Concurrency: Leverages threading to execute methods in the background, suitable for long-running tasks.
  • Scalability: The queue allows handling multiple requests, and the Scheduler can prioritize or batch them.
  • Thread Safety: The queue and Scheduler ensure safe concurrent access to the Servant.

Drawbacks

  • Complexity: The pattern introduces multiple components (Proxy, Queue, Scheduler, Servant), increasing design complexity.
  • Overhead: Enqueuing requests and managing Futures adds latency, especially for short tasks.
  • Resource Usage: The Scheduler thread and queue consume resources, even when idle.
  • Error Handling: Asynchronous errors require careful management to avoid silent failures.
  • Debugging: Tracing asynchronous execution across threads can be challenging.

When to Use

  • When you need to perform long-running or blocking operations (e.g., I/O, network calls) without blocking the client.
  • When you want to decouple method invocation from execution to improve modularity and responsiveness.
  • When you need to manage concurrent requests in a controlled manner (e.g., prioritizing or throttling).
  • When building distributed systems, event-driven applications, or reactive systems.

Real-World Examples

  • Distributed Systems: Remote procedure call (RPC) frameworks use Active Objects to handle asynchronous method calls across nodes.
  • Web Servers: Asynchronous request handling in servers like Netty or Vert.x uses similar principles.
  • Java Applications: Java’s CompletableFuture and ExecutorService can implement Active Objects for asynchronous method execution.
  • GUI Applications: Background tasks (e.g., file loading) are executed asynchronously to keep the UI responsive.

Use case and Implementation

Asynchronous Transaction Management in Banking using Active Object Pattern

// ActiveObjectPatternDemo.java
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

// BankAccount.java
interface BankAccount {
    void deposit(int amount);
    void withdraw(int amount);
    int getBalance();
}

// BankAccountImpl.java
class BankAccountImpl implements BankAccount {

    private int balance;
    private BlockingQueue<MethodRequest> requestQueue;

    public BankAccountImpl() {
        this.balance = 0;
        this.requestQueue = new LinkedBlockingQueue<>();
        new Scheduler().start();
    }

    @Override
    public void deposit(int amount) {
        MethodRequest request = new DepositMethodRequest(this, amount);
        requestQueue.offer(request);
    }

    @Override
    public void withdraw(int amount) {
        MethodRequest request = new WithdrawMethodRequest(this, amount);
        requestQueue.offer(request);
    }

    @Override
    public int getBalance() {
        return balance;
    }

    private class Scheduler extends Thread {
        @Override
        public void run() {
            while (true) {
                try {
                    MethodRequest request = requestQueue.take();
                    request.execute();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public synchronized void depositBalance(int amount) {
        balance += amount;
        System.out.println("New balance after deposit: $" + balance);
    }

    public synchronized void withdrawBalance(int amount) {
        if (balance >= amount) {
            balance -= amount;
            System.out.println("New balance after withdrawal: $" + balance);
        } else {
            System.out.println("Insufficient funds for withdrawal.");
        }
    }
}

// MethodRequest.java
interface MethodRequest {
    void execute();
}

// DepositMethodRequest.java
class DepositMethodRequest implements MethodRequest {
    private final BankAccountImpl account;
    private final int amount;

    public DepositMethodRequest(BankAccountImpl account, int amount) {
        this.account = account;
        this.amount = amount;
    }

    @Override
    public void execute() {
        System.out.println("Depositing $" + amount);
        account.depositBalance(amount);
    }
}

// WithdrawMethodRequest.java
class WithdrawMethodRequest implements MethodRequest {
    private final BankAccountImpl account;
    private final int amount;

    public WithdrawMethodRequest(BankAccountImpl account, int amount) {
        this.account = account;
        this.amount = amount;
    }

    @Override
    public void execute() {
        System.out.println("Withdrawing $" + amount);
        account.withdrawBalance(amount);
    }
}

// BankAccountProxy.java
class BankAccountProxy implements BankAccount {
    private final BankAccountImpl account;

    public BankAccountProxy() {
        this.account = new BankAccountImpl();
    }

    @Override
    public void deposit(int amount) {
        account.deposit(amount);
    }

    @Override
    public void withdraw(int amount) {
        account.withdraw(amount);
    }

    @Override
    public int getBalance() {
        return account.getBalance();
    }
}

// ActiveObjectPatternDemo.java
public class ActiveObjectPatternDemo {
    public static void main(String[] args) {
        BankAccount account = new BankAccountProxy();

        // Simulate operations
        account.deposit(100);
        account.withdraw(50);
        account.deposit(200);
        account.withdraw(150);

        // Sleep to allow time for the operations to process
        try {
            Thread.sleep(2000); // Give time for operations to finish
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Final balance: $" + account.getBalance());
    }
}

/*
C:\>javac ActiveObjectPatternDemo.java

C:\>java ActiveObjectPatternDemo
Depositing $100
New balance after deposit: $100
Withdrawing $50
New balance after withdrawal: $50
Depositing $200
New balance after deposit: $250
Withdrawing $150
New balance after withdrawal: $100
Final balance: $100
*/

The Active Object Pattern is a powerful concurrency design pattern that helps decouple method execution from method invocation to enhance the responsiveness of an application. In the provided banking example, operations like deposit and withdrawal are processed asynchronously through method request queues managed by a scheduler thread. This separation ensures that client threads are not blocked during execution and can continue with other tasks.

By using proxies, method requests, and a scheduler, the Active Object Pattern effectively manages concurrency without explicit locking in client code, thereby simplifying thread-safe designs. This is especially beneficial in real-time systems like banking, where maintaining UI responsiveness or handling multiple transactions concurrently is essential.

Scroll to Top