Object Pool
The Object Pool pattern is a creational design pattern that aims to improve performance by reusing objects that are expensive to create. Instead of creating a new object each time one is needed, the pool maintains a collection of pre-initialized objects. When an object is required, it’s borrowed from the pool; when it’s no longer needed, it’s returned to the pool for later use, rather than being destroyed. This reduces the overhead of frequent object creation and destruction, especially valuable when dealing with resource-intensive objects.
This pattern is particularly useful when object instantiation is slow or limited by external resources (e.g., database connections, network sockets, threads). It can significantly reduce latency and improve system throughput in scenarios involving high object churn, and helps manage resource constraints effectively. By limiting the overall number of objects created, it also contributes to better resource utilization and stability.
Usage
The Object Pool pattern is widely used in systems requiring efficient management of costly resources:
- Database Connection Pooling: Most database libraries and application servers utilize object pools to manage database connections. Establishing a database connection is a slow operation, so pooling these connections significantly improves performance.
- Thread Pooling: Similar to database connections, creating and destroying threads is expensive. Thread pools are essential components of concurrent programming, reusing threads to handle multiple tasks efficiently.
- Graphics and Game Development: Creating and disposing of graphical objects (textures, models, etc.) can be time-consuming. Object pools are used to reuse these objects, reducing lag and improving frame rates.
- Network Socket Management: Managing a large number of network sockets can be resource-intensive. Pooling sockets allows for efficient reuse and reduces the overhead of connection establishment and teardown.
Examples
-
Apache Commons Pool (Java): This library provides a generic object pooling framework for Java applications. It allows developers to easily create pools for various types of objects, including database connections, threads, and custom objects. Configuration options allow for controlling pool size, eviction policies, and validation logic. https://commons.apache.org/proper/commons-pool/
-
HikariCP (Java): Specifically designed for database connection pooling, HikariCP is a high-performance JDBC connection pool. It emphasizes speed and minimizes overhead, making it a popular choice for modern Java applications. It offers advanced features like connection validation, timeout handling, and monitoring. https://github.com/brettwooldridge/HikariCP
-
Unity Engine (C#): Unity uses object pooling extensively in game development for reusable game objects like bullets, enemies, and particle effects. The
Object.Instantiate()andObject.Destroy()methods can be slow within a game loop; using a pool avoids this performance bottleneck. Unity provides built-in tools and community-created asset store packages to facilitate object pooling. https://docs.unity3d.com/Manual/ObjectPooling.html
Specimens
15 implementationsThe Object Pool pattern manages a pool of reusable objects to reduce the cost of repeated object creation and destruction. It’s beneficial for heavy object creation scenarios, like database connections or complex calculations. The code implements a Pool class that holds a collection of PooledObject instances. request() and release() methods manage borrowing and returning objects to the pool. The PooledObject represents the objects being managed - in this case, simple objects with a given state. Using a Queue from Dart’s collection library offers an efficient FIFO structure to manage object availability. This implementation utilizes Dart’s class-based object-oriented structure for clarity and maintainability.
import 'dart:collection';
class PooledObject {
String state;
PooledObject(this.state);
void reset() {
state = ''; // Resets the object to a default state
}
@override
String toString() => 'PooledObject(state: $state)';
}
class Pool {
final int _maxSize;
final Queue<PooledObject> _pool = Queue();
Pool(this._maxSize) {
// Initialize the pool with pre-created objects
for (var i = 0; i < _maxSize; i++) {
_pool.add(PooledObject('Initial State'));
}
}
PooledObject request() {
if (_pool.isEmpty) {
print('Pool is empty. Creating a new object.');
return PooledObject('New State'); // Or handle this differently, e.g., throw an error.
}
return _pool.removeFirst();
}
void release(PooledObject object) {
object.reset();
if (_pool.length < _maxSize) {
_pool.addLast(object);
}
// Optionally, destroy objects if the pool is full
}
}
void main() {
var pool = Pool(3);
var obj1 = pool.request();
obj1.state = 'Object 1 in Use';
print(obj1);
var obj2 = pool.request();
obj2.state = 'Object 2 in Use';
print(obj2);
pool.release(obj1);
print('Object 1 released');
var obj3 = pool.request();
print(obj3);
pool.release(obj2);
pool.release(obj3);
print('Pool size after releases: ${pool.length}');
}
The Object Pool pattern manages a pool of reusable objects to reduce the overhead of frequent creation and destruction, especially for expensive-to-create resources. This implementation uses a Pool class to hold a fixed-size collection of generic typed objects. acquire retrieves an object from the pool, and release returns it for reuse. A reset method is included to prepare an object for re-use. The use of mutable.Queue for the pool and synchronized blocks for thread safety are idiomatic Scala approaches. This avoids garbage collection pressure and improves performance when dealing with resource intensive operations.
import scala.collection.mutable
class Pool[T](private val create: () => T, private val maxSize: Int) {
private val pool: mutable.Queue[T] = mutable.Queue()
// Initialize the pool
(0 until maxSize).foreach(_ => pool.enqueue(create()))
def acquire(): T = synchronized {
pool.dequeue()
}
def release(obj: T): Unit = synchronized {
reset(obj)
pool.enqueue(obj)
}
private def reset(obj: T): Unit = {
// Perform any necessary reset operations on the object here
// For example, reset internal state to default values.
//This is a no-op for now; type-specific reset logic would go here.
}
}
// Example usage (dummy ExpensiveObject)
case class ExpensiveObject(var value: Int = 0) {
def doSomething(): Unit = {
println(s"Doing something with value: $value")
}
}
object ObjectPoolExample {
def main(args: Array[String]): Unit = {
val pool = new Pool(ExpensiveObject, 5)
val obj1 = pool.acquire()
obj1.value = 10
obj1.doSomething()
pool.release(obj1)
val obj2 = pool.acquire()
obj2.value = 20
obj2.doSomething()
pool.release(obj2)
}
}
The Object Pool pattern manages a group of reusable objects, reducing the performance overhead of frequent object creation and destruction. Instead of creating a new object each time one is needed, the pool provides a pre-instantiated object from its collection. When the object is no longer needed, it’s returned to the pool, rather than destroyed, to be reused later. This example uses a simple DatabaseConnection class and a ConnectionPool to manage them, demonstrating how PHP can benefit from this pattern when dealing with resource-intensive operations. The use of a static pool instance and try...catch ensures proper resource handling and availability.
<?php
class DatabaseConnection {
private $host = 'localhost';
private $username = 'user';
private $password = 'password';
private $dbName = 'dbname';
private $connection;
private function __construct() {
$this->connection = new PDO("mysql:host=$this->host;dbname=$this->dbName", $this->username, $this->password);
$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
public function executeQuery(string $query): array {
return $this->connection->query($query)->fetchAll(PDO::FETCH_ASSOC);
}
public function close(): void {
$this->connection = null;
}
// Prevent direct instantiation
private static $instance = null;
public static function getInstance(): DatabaseConnection {
if(self::$instance === null){
self::$instance = new DatabaseConnection();
}
return self::$instance;
}
}
class ConnectionPool {
private $pool = [];
private $maxSize;
public function __construct(int $maxSize) {
$this->maxSize = $maxSize;
}
public function acquire(): DatabaseConnection {
if (empty($this->pool)) {
if (count($this->pool) < $this->maxSize) {
return new DatabaseConnection();
} else {
throw new Exception("Connection pool is exhausted.");
}
}
return array_pop($this->pool);
}
public function release(DatabaseConnection $connection): void {
if (count($this->pool) < $this->maxSize) {
$this->pool[] = $connection;
}
}
}
// Usage Example:
$pool = new ConnectionPool(5);
try {
$connection1 = $pool->acquire();
$results1 = $connection1->executeQuery("SELECT * FROM users");
print_r($results1);
$pool->release($connection1);
$connection2 = $pool->acquire();
$results2 = $connection2->executeQuery("SELECT * FROM products");
print_r($results2);
$pool->release($connection2);
} catch (Exception $e) {
echo "Error: " . $e->getMessage();
}
?>
The Object Pool pattern manages a group of reusable objects, reducing the overhead of frequent object creation and destruction. This is particularly useful for expensive-to-create objects. This Ruby implementation uses a simple array to store a pool of DatabaseConnection objects. checkout retrieves an available object (or creates one if the pool is empty). checkin returns an object to the pool for reuse. Using an array and pop/push efficiently manages object availability, fitting Ruby’s dynamic nature. The Mutex ensures thread safety when accessing the shared pool. It favors a pragmatic Ruby style, providing a basic reusable implementation.
require 'mutex'
class DatabaseConnection
def initialize
# Simulate expensive connection setup
sleep(0.5)
puts "Establishing database connection..."
@connected = true
end
def query(sql)
puts "Executing query: #{sql}"
# Simulate query execution
sleep(0.1)
"Query results"
end
def close
@connected = false
puts "Closing database connection..."
end
def connected?
@connected
end
end
class DatabaseConnectionPool
def initialize(size = 5)
@size = size
@pool = []
@mutex = Mutex.new
end
def checkout
@mutex.synchronize do
if @pool.empty?
puts "Pool empty, creating new connection..."
return DatabaseConnection.new
else
puts "Reusing connection from pool..."
return @pool.pop
end
end
end
def checkin(connection)
@mutex.synchronize do
if connection.connected? == false
puts "Connection is already closed. Discarding."
return
end
@pool.push(connection)
puts "Connection returned to pool. Pool size: #{@pool.size}"
end
end
end
# Example Usage
pool = DatabaseConnectionPool.new(2)
connection1 = pool.checkout
connection1.query("SELECT * FROM users")
pool.checkin(connection1)
connection2 = pool.checkout
connection2.query("SELECT * FROM products")
pool.checkin(connection2)
connection3 = pool.checkout #Might create a new connection, or reuse if available
connection3.query("UPDATE order SET status = 'shipped'")
pool.checkin(connection3)
The Object Pool pattern aims to reduce object creation/destruction overhead by maintaining a pool of reusable objects. Instead of creating new instances when needed, the pool provides existing ones, and returns them to the pool when finished. This is especially useful for expensive-to-create objects frequently used and discarded.
The Swift implementation utilizes a generics-based pool to handle any type conforming to a specific protocol, ensuring objects can be reset to a known state before reuse. The PooledObject protocol defines a reset() function for this purpose. The ObjectPool class manages a queue of available objects, providing borrowObject() and returnObject() methods. Swift’s automatic reference counting (ARC) handles memory management efficiently, and the use of a protocol allows for type safety and flexibility.
protocol PooledObject {
func reset()
}
class ObjectPool<T: PooledObject> {
private var pool: [T] = []
private let factory: () -> T
init(factory: @escaping () -> T, initialSize: Int = 10) {
self.factory = factory
for _ in 0..<initialSize {
pool.append(factory())
}
}
func borrowObject() -> T? {
if let object = pool.first {
pool.removeFirst()
return object
} else {
return factory()
}
}
func returnObject(_ object: T) {
object.reset()
pool.append(object)
}
}
class ExpensiveObject: PooledObject {
var data: String
init(data: String) {
self.data = data
}
func reset() {
self.data = ""
}
}
// Example Usage
let pool = ObjectPool<ExpensiveObject>(factory: { ExpensiveObject(data: "Initial Data") })
if let obj1 = pool.borrowObject() {
obj1.data = "Object 1 Used"
print("Using object 1: \(obj1.data)")
pool.returnObject(obj1)
}
if let obj2 = pool.borrowObject() {
print("Using object 2: \(obj2.data)") // Expect "" because of reset
}
The Object Pool pattern manages a pool of reusable objects to reduce the overhead of frequent object creation and destruction. This is particularly useful for expensive-to-create objects. The pattern involves a pool that holds available objects, and clients request objects from the pool instead of creating new ones. When an object is no longer needed, it’s returned to the pool for reuse.
Our Kotlin implementation uses a thread-safe ArrayBlockingQueue to store and retrieve pooled objects. PooledObject represents the reusable resource. The ObjectPool handles creating initial objects and providing/receiving them on demand. The use function ensures objects are returned to the pool after use, simplifying resource management and preventing leaks. Kotlin’s conciseness blends well with the pattern, making the pool management logic compact and readable.
import java.util.concurrent.ArrayBlockingQueue
class PooledObject {
var data: String = ""
fun reset() {
data = "" // Reset the object to a clean state
}
}
class ObjectPool(private val poolSize: Int) {
private val pool: ArrayBlockingQueue<PooledObject> = ArrayBlockingQueue(poolSize)
init {
// Initialize the pool with pre-created objects
for (i in 0 until poolSize) {
pool.put(PooledObject())
}
}
fun acquire(): PooledObject {
return pool.take()
}
fun release(obj: PooledObject) {
obj.reset()
pool.put(obj)
}
}
fun main() {
val pool = ObjectPool(5)
fun withPooledObject(block: (PooledObject) -> Unit) {
val obj = pool.acquire()
try {
block(obj)
} finally {
pool.release(obj)
}
}
repeat(10) {
withPooledObject { pooledObj ->
pooledObj.data = "Object $it"
println("Using object with data: ${pooledObj.data}")
}
}
}
The Object Pool pattern manages a collection of reusable objects to reduce the overhead of frequent creation and destruction. It improves performance, especially with resource-intensive objects. This Rust implementation uses a RefCell to allow interior mutability within the pool, and Mutex for thread-safe access. The get() method attempts to retrieve a free object; if none are available, it creates a new one (up to a limit). release() returns an object to the pool for reuse. This leverages Rust’s ownership and borrowing system while safely handling concurrent access through Mutex and RefCell, mirroring common practices for managing shared state in Rust.
use std::cell::RefCell;
use std::sync::{Arc, Mutex};
struct MyObject {
data: i32,
}
impl MyObject {
fn new(data: i32) -> Self {
println!("Creating new object with data: {}", data);
MyObject { data }
}
}
struct ObjectPool {
pool: Mutex<RefCell<Vec<MyObject>>>,
max_size: usize,
}
impl ObjectPool {
fn new(max_size: usize) -> Self {
ObjectPool {
pool: Mutex::new(RefCell::new(Vec::new())),
max_size,
}
}
fn get(&self) -> Option<MyObject> {
let mut pool = self.pool.lock().unwrap();
if let Some(obj) = pool.pop() {
Some(obj)
} else {
if pool.len() < self.max_size {
let new_obj = MyObject::new(42);
pool.push(new_obj);
Some(pool.pop().unwrap())
} else {
None
}
}
}
fn release(&self, obj: MyObject) {
let mut pool = self.pool.lock().unwrap();
pool.push(obj);
}
}
fn main() {
let pool = Arc::new(ObjectPool::new(3));
let pool_clone1 = Arc::clone(&pool);
let pool_clone2 = Arc::clone(&pool);
// Use objects from the pool
let obj1 = pool_clone1.get();
let obj2 = pool_clone2.get();
if let Some(o1) = obj1 {
println!("Object 1 data: {}", o1.data);
pool_clone1.release(o1);
}
if let Some(o2) = obj2 {
println!("Object 2 data: {}", o2.data);
pool_clone2.release(o2);
}
// Request more objects than the pool's max size
let mut handles = vec![];
for _ in 0..5 {
let pool_clone = Arc::clone(&pool);
handles.push(std::thread::spawn(move || {
if let Some(obj) = pool_clone.get() {
println!("Object from thread data: {}", obj.data);
pool_clone.release(obj);
} else {
println!("Object pool is full!");
}
}));
}
for handle in handles {
handle.join().unwrap();
}
}
The Object Pool pattern manages a pool of reusable objects to reduce the overhead of frequent object creation and destruction. This is particularly useful for expensive-to-create objects. The code implements a generic Object Pool using a channel-based approach. Pool manages a buffer of sync.Pool containing instances of type T. Get() retrieves an object from the pool, creating one if none are available. Release() returns an object to the pool for reuse. The sync.Pool’s Get() and Put() methods handle the actual allocation and release, making the implementation efficient and thread-safe, idiomatic to Go’s concurrency features.
package main
import (
"fmt"
"sync"
)
// ObjectPool generic pool
type Pool[T any] struct {
pool sync.Pool
}
// NewPool creates a new object pool.
func NewPool[T any]() *Pool[T] {
return &Pool[T]{}
}
// Get retrieves an object from the pool.
func (p *Pool[T]) Get() *T {
val := p.pool.Get()
if val == nil {
return new(T) // Create a new object if the pool is empty
}
return val.(*T)
}
// Release returns an object to the pool.
func (p *Pool[T]) Release(obj *T) {
p.pool.Put(obj)
}
// Example usage with a simple struct
type MyObject struct {
ID int
}
func main() {
pool := NewPool[MyObject]()
// Get some objects
obj1 := pool.Get()
obj1.ID = 1
obj2 := pool.Get()
obj2.ID = 2
fmt.Println("Object 1:", obj1)
fmt.Println("Object 2:", obj2)
// Release the objects back to the pool
pool.Release(obj1)
pool.Release(obj2)
// Get objects again - might get reused ones
obj3 := pool.Get()
fmt.Println("Object 3:", obj3)
// Demonstrate that memory is recycled. This isn't a strict test but shows usage.
pool.Release(obj3)
}
The Object Pool pattern manages a pool of reusable objects to reduce the overhead of frequent object creation and destruction. Instead of allocating new memory each time an object is needed, the pool provides an existing object. When an object is no longer needed, it’s returned to the pool for later reuse. This improves performance, especially for expensive-to-create objects. The C implementation utilizes a fixed-size array to store pre-allocated objects. pool_get() retrieves an available object, while pool_return() places an unused object back into the pool. This is a low-level, manual approach fitting C’s style, offering direct memory control and efficiency without relying on runtime type information or garbage collection.
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
// Define the type of objects we'll pool
typedef struct {
int data;
bool in_use;
} MyObject;
// Object Pool structure
typedef struct {
MyObject* objects;
size_t pool_size;
size_t next_free;
} ObjectPool;
// Initialize the object pool
ObjectPool* pool_init(size_t size) {
ObjectPool* pool = (ObjectPool*)malloc(sizeof(ObjectPool));
if (!pool) {
return NULL;
}
pool->objects = (MyObject*)malloc(size * sizeof(MyObject));
if (!pool->objects) {
free(pool);
return NULL;
}
pool->pool_size = size;
pool->next_free = 0;
for (size_t i = 0; i < size; ++i) {
pool->objects[i].in_use = false;
}
return pool;
}
// Get an object from the pool
MyObject* pool_get(ObjectPool* pool) {
if (pool->next_free < pool->pool_size) {
MyObject* obj = &pool->objects[pool->next_free];
obj->in_use = true;
pool->next_free++;
return obj;
}
return NULL; // Pool is empty
}
// Return an object to the pool
void pool_return(ObjectPool* pool, MyObject* obj) {
if (obj && obj->in_use && (obj - &pool->objects[0]) < pool->pool_size) {
obj->in_use = false;
pool->next_free--; // Adjust to re-use the slot
}
}
// Destroy the object pool
void pool_destroy(ObjectPool* pool) {
if (pool) {
if (pool->objects) {
free(pool->objects);
}
free(pool);
}
}
int main() {
ObjectPool* pool = pool_init(5);
if (!pool) {
fprintf(stderr, "Failed to initialize pool\n");
return 1;
}
MyObject* obj1 = pool_get(pool);
MyObject* obj2 = pool_get(pool);
if (obj1 && obj2) {
obj1->data = 10;
obj2->data = 20;
printf("Object 1 data: %d\n", obj1->data);
printf("Object 2 data: %d\n", obj2->data);
}
pool_return(pool, obj1);
pool_return(pool, obj2);
MyObject* obj3 = pool_get(pool);
if(obj3) {
printf("Object 3 data (reused): %d\n", obj3->data);
}
pool_destroy(pool);
return 0;
}
The Object Pool pattern manages a pool of reusable objects to reduce the overhead of frequent object creation and destruction. Instead of allocating new objects directly, clients request them from the pool. The pool provides existing available objects, or creates new ones if the pool is empty (up to a predefined maximum size). When an object is no longer needed, it’s returned to the pool for reuse, rather than being destroyed.
This C++ implementation uses a std::vector to store the pooled objects. The Pool class provides acquire() and release() methods to manage object access. A simple PooledObject class is used as an example. The use of a std::mutex ensures thread safety. This approach is idiomatic C++ as it leverages standard containers and synchronization primitives, promoting efficiency and avoiding manual memory management where possible. Templates allow the pool to manage objects of any type.
#include <iostream>
#include <vector>
#include <mutex>
template <typename T>
class Pool {
public:
Pool(size_t size) : pool_size(size) {
for (size_t i = 0; i < pool_size; ++i) {
pool.push_back(std::make_unique<T>());
}
}
T* acquire() {
std::lock_guard<std::mutex> lock(mutex);
if (pool.empty()) {
return nullptr; // Or throw an exception
}
T* object = pool.back().get();
pool.pop_back();
return object;
}
void release(T* object) {
std::lock_guard<std::mutex> lock(mutex);
pool.push_back(std::unique_ptr<T>(object));
}
private:
size_t pool_size;
std::vector<std::unique_ptr<T>> pool;
std::mutex mutex;
};
class PooledObject {
public:
PooledObject(int id) : id_(id) {
std::cout << "PooledObject created with ID: " << id_ << std::endl;
}
~PooledObject() {
std::cout << "PooledObject destroyed with ID: " << id_ << std::endl;
}
int getId() const { return id_; }
private:
int id_;
};
int main() {
Pool<PooledObject> objectPool(5);
PooledObject* obj1 = objectPool.acquire();
if (obj1) {
std::cout << "Acquired object with ID: " << obj1->getId() << std::endl;
}
PooledObject* obj2 = objectPool.acquire();
if(obj2){
std::cout << "Acquired object with ID: " << obj2->getId() << std::endl;
}
objectPool.release(obj1);
objectPool.release(obj2);
return 0;
}
The Object Pool pattern manages a pool of reusable objects to reduce the performance overhead of frequent object creation and destruction. Instead of allocating new objects each time one is needed, the pool provides existing instances. When an object is no longer required, it’s returned to the pool for later reuse. This is particularly beneficial for expensive-to-create objects.
The C# implementation uses a generic class ObjectPool<T> to manage the pool. It maintains a List<T> of available objects and provides Get() and Release() methods. Get() either retrieves a free object or creates a new one if the pool is empty (up to a maximum size). Release() returns an object to the pool, making it available for reuse. The use of generics makes the pool type-safe and reusable. The using statement ensures objects are always returned to the pool, even in the event of exceptions.
using System;
using System.Collections.Generic;
public class ObjectPool<T> where T : new()
{
private readonly List<T> _pool = new List<T>();
private readonly int _maxSize;
public ObjectPool(int maxSize)
{
_maxSize = maxSize;
}
public T Get()
{
lock (_pool)
{
if (_pool.Count > 0)
{
return _pool.Dequeue();
}
if (_pool.Count < _maxSize)
{
return new T();
}
throw new InvalidOperationException("Pool is exhausted.");
}
}
public void Release(T obj)
{
lock (_pool)
{
_pool.Enqueue(obj);
}
}
}
// Example Usage
public class ExpensiveObject : IDisposable
{
public ExpensiveObject()
{
Console.WriteLine("Expensive object created.");
}
public void DoSomething()
{
Console.WriteLine("Doing something with the expensive object.");
}
public void Dispose()
{
Console.WriteLine("Expensive object disposed.");
}
}
public class Example
{
public static void Main(string[] args)
{
var pool = new ObjectPool<ExpensiveObject>(3);
for (int i = 0; i < 5; i++)
{
using (var obj = pool.Get())
{
obj.DoSomething();
}
Console.WriteLine($"Iteration {i+1} complete.");
}
}
}
The Object Pool pattern manages a collection of reusable objects, reducing the overhead of frequent object creation and destruction. Instead of allocating a new object each time one is needed, the pool provides an existing, pre-initialized object. When the object is no longer required, it’s returned to the pool, not discarded. This improves performance, especially for expensive-to-create objects.
The TypeScript implementation uses a generic class ObjectPool to manage objects of any type. borrowObject retrieves an object from the pool (creating one if none are available), and returnObject adds it back. The resetState method is crucial for ensuring borrowed objects are in a known, clean state. Using a class and generics is idiomatic TypeScript, promoting type safety and reusability. The pool uses a simple array for storage, suitable for many use cases, and can be easily adapted to use more sophisticated data structures if needed.
class ObjectPool<T> {
private pool: T[] = [];
private factory: () => T;
private resetState: (obj: T) => void;
constructor(factory: () => T, resetState: (obj: T) => void) {
this.factory = factory;
this.resetState = resetState;
}
borrowObject(): T {
if (this.pool.length > 0) {
return this.pool.pop()!;
} else {
return this.factory();
}
}
returnObject(obj: T): void {
this.resetState(obj);
this.pool.push(obj);
}
}
// Example Usage:
interface Vector2 {
x: number;
y: number;
}
const vectorFactory = () => ({ x: 0, y: 0 });
const vectorReset = (vector: Vector2) => {
vector.x = 0;
vector.y = 0;
};
const pool = new ObjectPool<Vector2>(vectorFactory, vectorReset);
const vec1 = pool.borrowObject();
vec1.x = 10;
vec1.y = 20;
console.log("Borrowed vec1:", vec1);
pool.returnObject(vec1);
const vec2 = pool.borrowObject();
console.log("Borrowed vec2 (potentially recycled vec1):", vec2);
The Object Pool pattern reduces the overhead of frequently creating and destroying objects. Instead of allocating new objects each time, it reuses pre-initialized objects from a pool. This is particularly useful for expensive-to-create objects.
The code implements a simple Object Pool for a ReusableObject class. The Pool class manages a collection of these objects. acquire() retrieves an object from the pool (creating one if none are available), and release() returns an object to the pool for reuse. The use of a simple array for storage and methods like pop() and push() are idiomatic JavaScript for managing a collection in this way. The constructor initializes the pool with a specified number of objects.
class ReusableObject {
constructor(data) {
this.data = data;
this.isUsed = false;
// Simulate expensive initialization
for (let i = 0; i < 1000000; i++) {
Math.sqrt(i);
}
}
reset() {
this.data = null;
this.isUsed = false;
}
use() {
this.isUsed = true;
console.log("Object used with data:", this.data);
}
}
class Pool {
constructor(size) {
this.pool = [];
this.size = size;
for (let i = 0; i < size; i++) {
this.pool.push(new ReusableObject(null));
}
}
acquire() {
if (this.pool.length > 0) {
const obj = this.pool.pop();
obj.use(); // Indicate object is in use
return obj;
}
// Create a new object if the pool is empty
return new ReusableObject(null);
}
release(obj) {
obj.reset();
this.pool.push(obj);
}
}
// Example Usage:
const pool = new Pool(5);
let obj1 = pool.acquire();
obj1.data = "Data 1";
let obj2 = pool.acquire();
obj2.data = "Data 2";
pool.release(obj1);
pool.release(obj2);
let obj3 = pool.acquire(); // Might reuse obj1 or obj2
obj3.data = "Data 3";
The Object Pool pattern manages a pool of reusable objects to reduce the overhead of frequent object creation and destruction. This is particularly useful for expensive-to-create objects. The pool maintains a collection of objects, and instead of creating new ones, clients request objects from the pool. When an object is no longer needed, it’s returned to the pool for reuse.
This Python implementation uses a simple list to store the available objects. PooledObject represents the type of object being pooled. ObjectPool handles object creation (up to a maximum size) and distribution. acquire_object retrieves an object, creating one if needed and available. release_object returns an object to the pool. This approach aligns with Python’s dynamic nature and avoids complex synchronization for basic use cases. More robust implementations might use threading locks for concurrent access.
class PooledObject:
def __init__(self, id):
self.id = id
self.reset()
def reset(self):
# Simulate expensive initialization/resetting
print(f"Object {self.id} resetting...")
self.value = 0
def do_something(self):
self.value += 1
print(f"Object {self.id} did something. Value: {self.value}")
class ObjectPool:
def __init__(self, obj_class, max_size):
self.obj_class = obj_class
self.max_size = max_size
self.pool = []
self._create_pool()
def _create_pool(self):
for i in range(self.max_size):
self.pool.append(self.obj_class(i))
def acquire_object(self):
if self.pool:
return self.pool.pop()
else:
return self.obj_class(len(self.pool)) # Create if pool is empty
def release_object(self, obj):
obj.reset()
self.pool.append(obj)
if __name__ == "__main__":
pool = ObjectPool(PooledObject, 3)
obj1 = pool.acquire_object()
obj1.do_something()
obj1.do_something()
obj2 = pool.acquire_object()
obj2.do_something()
pool.release_object(obj1)
obj3 = pool.acquire_object()
obj3.do_something()
pool.release_object(obj2)
pool.release_object(obj3)
The Object Pool pattern manages a pool of reusable objects to avoid the expensive operation of repeatedly creating and destroying them. This is particularly useful for objects that are resource-intensive to initialize. The code demonstrates a simple generic Object Pool using a java.util.concurrent.ArrayBlockingQueue to store available objects. acquireObject() blocks if no object is available, and releaseObject() returns an object to the pool. The PooledObjectFactory interface allows for custom object creation. This implementation leverages Java’s generics for type safety and uses a blocking queue for thread-safe access, fitting idiomatic Java concurrency practices.
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
interface PooledObjectFactory<T> {
T createObject();
}
public class ObjectPool<T> {
private final BlockingQueue<T> pool;
private final PooledObjectFactory<T> factory;
private final int poolSize;
public ObjectPool(int poolSize, PooledObjectFactory<T> factory) {
this.poolSize = poolSize;
this.factory = factory;
this.pool = new ArrayBlockingQueue<>(poolSize);
// Pre-populate the pool
for (int i = 0; i < poolSize; i++) {
pool.add(factory.createObject());
}
}
public T acquireObject() throws InterruptedException {
return pool.take(); // Blocks until an object is available
}
public void releaseObject(T obj) {
if (pool.offer(obj)) {
// Object added back to the pool successfully
} else {
// Pool is full, potentially log or handle the situation
System.err.println("Pool is full. Object discarded.");
}
}
public int getPoolSize() {
return poolSize;
}
public int getAvailableObjects() {
return pool.size();
}
public static void main(String[] args) throws InterruptedException {
// Example Usage with a simple object
ObjectPool<String> stringPool = new ObjectPool<>(5, String::new);
System.out.println("Available objects: " + stringPool.getAvailableObjects());
String obj1 = stringPool.acquireObject();
obj1 = "Hello";
System.out.println("Acquired object: " + obj1);
System.out.println("Available objects: " + stringPool.getAvailableObjects());
stringPool.releaseObject(obj1);
System.out.println("Released object. Available objects: " + stringPool.getAvailableObjects());
}
}