Fading Coder

One Final Commit for the Last Sprint

Home > Notes > Content

Building a JDBC Connection Pool with Java Concurrency Primitives

Notes May 9 3

A lightweight, in-memory connection pool can be implemented with nothing more than java.sql.Connection stubs, a LinkedList, and the classic wait/notify mechanism. Below you’ll find a complete walk-through that covers stub generation, pool logic, timeout handdling, and a stress-test harness.

  1. Stubbing a JDBC Connection

Because we only need a placeholder object that implements java.sql.Connection, a dynamic proxy is the simplest approach:

package pool.stub;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;

public final class StubFactory {

    private static class NoOpHandler implements InvocationHandler {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) {
            // Every method returns null; real pools would delegate to a driver.
            return null;
        }
    }

    public static Connection newStub() {
        return (Connection) Proxy.newProxyInstance(
                StubFactory.class.getClassLoader(),
                new Class>[]{Connection.class},
                new NoOpHandler());
    }
}

  1. Core Pool Implementation

The pool keeps available stubs in a LinkedList. All access is guarded by the list’s intrinsic lock to ensure visibility and mutual exclusion.

package pool.core;

import pool.stub.StubFactory;

import java.sql.Connection;
import java.util.LinkedList;

public final class SimplePool {
    private final LinkedList<Connection> free = new LinkedList<>();

    public SimplePool(int initialSize) {
        for (int i = 0; i < initialSize; i++) {
            free.addLast(StubFactory.newStub());
        }
    }

    /**

     * Returns a stub to the pool and wakes up any waiting threads.
     */
    public void release(Connection c) {
        if (c == null) return;
        synchronized (free) {
            free.addLast(c);
            free.notifyAll();
        }
    }

    /**
     * Tries to obtain a stub within the given timeout.
     * @param timeoutMillis 0 means infinite wait.
     */
    public Connection acquire(long timeoutMillis) throws InterruptedException {
        synchronized (free) {
            if (timeoutMillis <= 0) {
                while (free.isEmpty()) {
                    free.wait();
                }
                return free.removeFirst();
            }

            long deadline = System.currentTimeMillis() + timeoutMillis;
            long remaining = timeoutMillis;
            while (free.isEmpty() && remaining > 0) {
                free.wait(remaining);
                remaining = deadline - System.currentTimeMillis();
            }
            return free.isEmpty() ? null : free.removeFirst();
        }
    }
}

  1. Stress-Testing the Pool

We simulate 1 000 concurrent threads that each try to borrow and return a stub 20 times. Two CountDownLatch objects are used:

  • startGate – released once all threads are ready, ensuring they hit the pool at the same moment.
  • endGate – counts down every time a thread finishes, letting the main thread await completion.
package pool.test;

import pool.core.SimplePool;

import java.sql.Connection;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

public final class PoolStressTest {
    private static final SimplePool POOL = new SimplePool(10);

    public static void main(String[] args) throws InterruptedException {
        final int threads = 1000;
        final int iterations = 20;
        final CountDownLatch startGate = new CountDownLatch(1);
        final CountDownLatch endGate   = new CountDownLatch(threads);

        final AtomicInteger acquired = new AtomicInteger();
        final AtomicInteger timedOut = new AtomicInteger();

        for (int i = 0; i < threads; i++) {
            new Thread(() -> {
                try {
                    startGate.await(); // wait for the starting gun
                    for (int j = 0; j < iterations; j++) {
                        Connection c = POOL.acquire(1); // 1 ms timeout
                        if (c != null) {
                            try {
                                c.createStatement(); // pretend to use it
                            } finally {
                                POOL.release(c);
                                acquired.incrementAndGet();
                            }
                        } else {
                            timedOut.incrementAndGet();
                        }
                    }
                } catch (InterruptedException ignored) {
                } finally {
                    endGate.countDown();
                }
            }).start();
        }

        startGate.countDown(); // fire!
        endGate.await();       // wait for everyone to finish

        System.out.println("Total attempts: " + (threads * iterations));
        System.out.println("Successful borrows: " + acquired.get());
        System.out.println("Timeouts: " + timedOut.get());
    }
}

Typical Output

Total attempts: 20000
Successful borrows: 11914
Timeouts: 8086

Raising the timeout to 100 ms shifts the balance dramatically:

Total attempts: 20000
Successful borrows: 19050
Timeouts: 950

The experiment shows how wait/notify plus timeout parameters can be tuned to trade latency against availability in a hand-rolled JDBC connection pool.

Related Articles

Designing Alertmanager Templates for Prometheus Notifications

How to craft Alertmanager templates to format alert messages, improving clarity and presentation. Alertmanager uses Go’s text/template engine with additional helper functions. Alerting rules referenc...

Deploying a Maven Web Application to Tomcat 9 Using the Tomcat Manager

Tomcat 9 does not provide a dedicated Maven plugin. The Tomcat Manager interface, however, is backward-compatible, so the Tomcat 7 Maven Plugin can be used to deploy to Tomcat 9. This guide shows two...

Skipping Errors in MySQL Asynchronous Replication

When a replica halts because the SQL thread encounters an error, you can resume replication by skipping the problematic event(s). Two common approaches are available. Methods to Skip Errors 1) Skip a...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.