Building a Lightweight Java Object Pool Without Commons‑Pool2

This article walks through the design and implementation of a simple, thread‑safe object pool in Java using a LinkedBlockingQueue, explains the underlying concepts such as factory pattern and queue trimming, and provides a complete code example with a test script and output analysis.

FunTester
FunTester
FunTester
Building a Lightweight Java Object Pool Without Commons‑Pool2

A lightweight object‑pool utility was created to replace the heavyweight commons-pool2 in simple scenarios. The pool caches objects in a thread‑safe queue, allowing reuse without the overhead of frequent allocation and garbage collection.

Design Overview

Objects are stored in a LinkedBlockingQueue, which provides built‑in thread safety.

Public methods borrow() and back() expose borrowing and returning functionality.

An optional trimQueue(int size) method can shrink the pool to a desired maximum size, preventing unbounded memory growth.

The pool does not enforce a hard size limit; callers can decide whether to limit the number of cached instances.

Implementation

package com.funtester.funpool;

import java.util.concurrent.LinkedBlockingQueue;

/**
 * Simple generic object pool.
 * Uses a LinkedBlockingQueue to store objects, a factory to create new instances,
 * and provides borrow, return and trimming operations.
 */
class FunPool<F> {
    /** Factory that creates new objects of type F */
    private final FunPooledFactory<F> factory;
    /** Thread‑safe queue that holds idle objects */
    private final LinkedBlockingQueue<F> pool = new LinkedBlockingQueue<>();

    FunPool(FunPooledFactory<F> factory) {
        this.factory = factory;
    }

    /** Borrow an object from the pool. If the pool is empty a new instance is created. */
    F borrow() {
        F obj = pool.poll();
        if (obj == null) {
            obj = factory.newInstance();
        }
        return obj;
    }

    /** Return an object to the pool. If the queue is full the object is discarded. */
    void back(F obj) {
        boolean offered = pool.offer(obj);
        if (!offered) {
            // pool is at capacity; let GC reclaim the object
            obj = null;
        }
    }

    /** Current number of idle objects in the pool. */
    int size() {
        return pool.size();
    }

    /** Trim the pool so that its size does not exceed the supplied limit. */
    void trimQueue(int maxSize) {
        while (size() > maxSize) {
            pool.poll();
        }
    }
}

Factory interface used by the pool:

package com.funtester.funpool;

/** Functional interface for creating new pool objects. */
interface FunPooledFactory<F> {
    /** Create a fresh instance of type F. */
    F newInstance();
}

Key Concepts

Object‑pool pattern: Reusing pre‑created objects reduces allocation latency and GC pressure in high‑throughput applications.

Factory pattern: The FunPooledFactory decouples object creation from the pool, allowing any class to be pooled without modifying the pool code.

Thread safety: LinkedBlockingQueue guarantees safe concurrent poll and offer operations.

Borrow/return semantics: borrow() retrieves an idle instance or creates a new one; back() attempts to place the instance back into the queue, discarding it if the queue is already full.

Size control: trimQueue(int maxSize) can be called periodically to bound memory usage.

Example Usage

static void main(String[] args) {
    // Create a pool for Demo objects using an anonymous factory implementation
    FunPool<Demo> pool = new FunPool<>(new FunPooledFactory<Demo>() {
        @Override
        public Demo newInstance() {
            return new Demo();
        }
    });

    // First borrow – pool is empty, a new Demo is created
    Demo first = pool.borrow();
    System.out.println(first.hashCode());

    // Borrow two more times, returning each after use
    for (int i = 0; i < 2; i++) {
        Demo obj = pool.borrow();
        System.out.println(obj.hashCode());
        pool.back(obj);
    }

    // Show how many idle objects remain in the pool
    System.out.println("Pool size: " + pool.size());
}

/** Simple placeholder class used in the demo */
static class Demo {}

Typical console output demonstrates that the first borrowed instance is distinct, while the subsequent two borrow/return cycles reuse the same object, confirming correct pooling behavior:

1528769018
878991463
878991463
Pool size: 1

This minimal pool can be integrated into Java or Groovy projects where full‑featured pooling libraries are unnecessary, offering a straightforward way to improve performance and resource utilization.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

BackendJavaperformanceconcurrencydesign patternobject pool
FunTester
Written by

FunTester

10k followers, 1k articles | completely useless

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.