Object Pool Pattern
Reuse expensive objects with an object pool: thread-safe checkout, return, health validation, and eviction.
import java.util.concurrent.*;
import java.util.function.*;
public class ObjectPool<T> {
private final BlockingQueue<T> pool;
private final Supplier<T> factory;
private final Predicate<T> validator;
private final Consumer<T> destroyer;
private final int maxSize;
public ObjectPool(
int maxSize,
Supplier<T> factory,
Predicate<T> validator,
Consumer<T> destroyer
) {
this.maxSize = maxSize;
this.factory = factory;
this.validator = validator;
this.destroyer = destroyer;
this.pool = new LinkedBlockingQueue<>(maxSize);
// Pre-populate
for (int i = 0; i < maxSize; i++) {
pool.offer(factory.get());
}
}
// Borrow object
public T borrow(long timeoutMs) throws Exception {
T obj = pool.poll(timeoutMs, TimeUnit.MILLISECONDS);
if (obj == null) {
throw new TimeoutException("Pool exhausted");
}
// Validate before returning
if (!validator.test(obj)) {
destroyer.accept(obj);
return factory.get(); // create fresh
}
return obj;
}
// Return object
public void release(T obj) {
if (obj == null) return;
if (validator.test(obj)) {
if (!pool.offer(obj)) {
destroyer.accept(obj); // pool full
}
} else {
destroyer.accept(obj);
pool.offer(factory.get()); // replace with fresh
}
}
// Auto-closeable lease
public Lease<T> lease(long timeoutMs) throws Exception {
return new Lease<>(borrow(timeoutMs), this);
}
public int available() { return pool.size(); }
// Lease with auto-return
public static class Lease<T> implements AutoCloseable {
private final T object;
private final ObjectPool<T> pool;
private boolean returned = false;
Lease(T object, ObjectPool<T> pool) {
this.object = object;
this.pool = pool;
}
public T get() { return object; }
@Override
public void close() {
if (!returned) {
pool.release(object);
returned = true;
}
}
}
// Example: pooled StringBuilder
public static void main(String[] args) throws Exception {
ObjectPool<StringBuilder> pool = new ObjectPool<>(
5,
() -> new StringBuilder(256),
sb -> sb.length() < 10_000, // reject if too large
sb -> {} // no cleanup needed
);
System.out.println("Available: " + pool.available()); // 5
try (var lease = pool.lease(1000)) {
StringBuilder sb = lease.get();
sb.setLength(0);
sb.append("Hello from pooled StringBuilder!");
System.out.println(sb);
} // auto-returned
System.out.println("Available: " + pool.available()); // 5
}
}Use Cases
- Reusing expensive-to-create objects
- Database connection pooling patterns
- Thread pool and worker management
Tags
Related Snippets
Similar patterns you can reuse in the same workflow.
Builder Pattern — Fluent Object Construction
Implement the Builder pattern for complex objects with validation, immutability, and method chaining.
Best for: Constructing complex objects with many optional parameters
Singleton Pattern — Thread-Safe Approaches
Implement thread-safe singletons in Java: enum, holder class, double-checked locking, and eager init.
Best for: Application-wide configuration managers
Strategy Pattern with Lambdas
Implement the Strategy pattern using interfaces and Java lambdas for flexible algorithm selection.
Best for: Swappable pricing or discount algorithms
Factory Method Pattern with Registry
Implement the Factory pattern using a registry map for extensible object creation without switch statements.
Best for: Extensible object creation in plugin architectures