Design Pattern

Object Pool Pattern

Clean Java-only production-ready implementation.


What Problem It Solves

Creating objects (DB connections, threads, sockets) is expensive. Instead of creating and destroying them per request, maintain a pool of reusable objects. Borrow, use, return.

When to Use

SignalPool Pattern
Object creation takes > 10 msReuse across requests
Object wraps an external resource (socket, DB handle)Pool manages lifespan
Peak load >> average loadPre-warm minimum, grow on demand

Implementation

// โ”€โ”€โ”€ EXAMPLE 1 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
// WHAT WE ARE IMPLEMENTING:
// A database connection manager reusing a static set of heavy socket
// connections.
//
// WHERE THE PATTERN FITS IN:
// DatabaseConnectionPool acts as the Pool manager, tracking active and idle
// Connection client instances to throttle load.
// โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

// --- Pooled resource ---
class DatabaseConnection {
    private final int id;
    private boolean inUse = false;

    public DatabaseConnection(int id) {
        this.id = id;
        System.out.println("  [Pool] Creating connection " + id);
    }

    public boolean query(String sql) {
        if (!inUse) throw new IllegalStateException("Connection not checked out");
        System.out.println("  [Conn " + id + "] Executing: " + sql);
        return true;
    }

    void markInUse(boolean inUse) { this.inUse = inUse; }
}

// --- Object Pool ---
class ConnectionPool {
    private final BlockingQueue<DatabaseConnection> available = new LinkedBlockingQueue<>();
    private final AtomicInteger created = new AtomicInteger(0);
    private final int maxSize;

    public ConnectionPool(int maxSize) {
        this.maxSize = maxSize;
        // Pre-warm: create minimum connections
        for (int i = 0; i < Math.min(3, maxSize); i++) {
            available.add(createConnection());
        }
    }

    private DatabaseConnection createConnection() {
        return new DatabaseConnection(created.incrementAndGet());
    }

    public DatabaseConnection borrow() throws InterruptedException {
        DatabaseConnection conn = available.poll();
        if (conn == null) {
            if (created.get() < maxSize) {
                conn = createConnection();
            } else {
                // Wait for a connection to be returned (blocking)
                conn = available.take();
            }
        }
        conn.markInUse(true);
        return conn;
    }

    public void release(DatabaseConnection conn) {
        conn.markInUse(false);
        available.offer(conn);
        System.out.println("  [Pool] Connection " + conn + " returned to pool");
    }

    public int availableCount() { return available.size(); }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        ConnectionPool pool = new ConnectionPool(5);

        // Borrow and use
        DatabaseConnection c1 = pool.borrow();
        c1.query("SELECT * FROM users");

        DatabaseConnection c2 = pool.borrow();
        c2.query("INSERT INTO orders ...");

        // Return to pool
        pool.release(c1);
        pool.release(c2);

        System.out.println("Available connections: " + pool.availableCount());
    }
}

๐Ÿ’ฌReview

Help Us Improve

How helpful was this walkthrough?

Click a star to rate. We actively use this feedback to refine and update our system design content.

Placeholder
Optional but highly appreciated!

Discussion

Share your thoughts, ask questions, or help others.

Loading comments...