The objective is to create a lightweight object pool that should be as simple as possible, but not simpler. It should be able to create and pool any type of object, be configurable (i.e. pool size) and be able to be replenish exhausted pools or timeout waiting for returned objects. It should be thread safe and able to operate at high volumes (borrow, use and return 300 objects per second). I should mention that the overall objective is to make the use of expensive to create objects more performant.
Based on a number of articles and personal experience I have written the following code, and the code has its own Github repository. I welcome any comment regarding this code but in particular I would appreciate feedback on the following points:
- Is there a glaring issue with the code not satisfying the above specified concerns?
- Concurrency not being my strong point, I suspect that there are issue related to threading. Please let me know if you find any.
- Comments regarding style, naming, structure etc are very welcome.
- What features are missing that you would like to see in a simple object pool?
An object pool must implement the AbstractObjectPool class:
public abstract class AbstractObjectPool<T> implements ObjectPool {
private AbstractQueue<T> pool;
/**
* Initialise the pool and populate it with poolSize number of objects
*
* @param poolSize the size of object to initialise the pool
* @param pool the abstract queue pool
*/
protected void initialize(int poolSize, AbstractQueue<T> pool) {
setPool(pool);
for (int i = 0; i < poolSize; i++) {
pool.add(createObject());
}
}
/**
* Gets the pool
*
* @return AbstractQueue pool object
*/
public AbstractQueue<T> getPool() {
return pool;
}
/**
* Sets the pool.
*
* @param pool the pool to set
*/
private void setPool(AbstractQueue<T> pool) {
this.pool = pool;
}
/**
* Gets the next free object from the pool.
* <p/>
* Different strategies can be implemented to deal with a
* situation where the pool doesn't contain any objects.
* <p/>
* Some possible options:
* <p/>
* 1. a new object will be created and given to the caller of this method.
* 2. a PoolDepletionException is thrown
* 3. wait for a specified time for an object to be returned
*
* @return T borrowed object
* @throws PoolDepletionException thrown if the pool has been depleted
* @throws InterruptedException
*/
public abstract T borrowObject() throws PoolDepletionException, InterruptedException;
/**
* Returns object back to the pool.
* <p/>
* Possible implementation may include code to clean/reset the
* object to initial values.
*
* @param object object to be returned
*/
public void returnObject(T object) {
if (object == null) {
return;
}
this.pool.offer(object);
}
/**
* Creates a new object.
*
* @return T new object
*/
protected abstract T createObject();
/**
* Destroys an object.
*
* @param pool the pool to destroy
*/
protected void destroyObject(AbstractQueue<T> pool) {
T object = pool.poll();
object = null;
}
/**
* Destroys the entire pool.
*/
public void destroyPool() {
while (pool != null && !pool.isEmpty()) {
destroyObject(pool);
}
pool = null;
}
}
Here is an implementation for the BOON JSON parser object (https://github.com/boonproject/boon/wiki/Boon-JSON-in-five-minutes).
public class JsonParserFixedPool extends AbstractObjectPool<JsonParserAndMapper> {
private LinkedBlockingQueue<JsonParserAndMapper> pool = new LinkedBlockingQueue<>();
private int pollTimeout = 3000;
public JsonParserFixedPool() {
initialize(20, pool);
}
public JsonParserFixedPool(int poolSize) {
initialize(poolSize, pool);
}
public JsonParserFixedPool(int poolSize, int pollTimeout) {
initialize(poolSize, pool);
this.pollTimeout = pollTimeout;
}
/**
* Borrow one BOON JSON parser object from the pool.
*
* @return returns one BOON JSON parser object
* @throws PoolDepletionException thrown if there are no BOON JSON parser objects in the pool
* @throws InterruptedException
*/
@Override
public JsonParserAndMapper borrowObject() throws PoolDepletionException, InterruptedException {
JsonParserAndMapper jsonParserAndMapper = pool.poll(this.pollTimeout, TimeUnit.MILLISECONDS);
if (jsonParserAndMapper == null) {
throw new PoolDepletionException("The JSON parser pool is empty and was not replenished within timeout limits.");
}
return jsonParserAndMapper;
}
/**
* Creates a BOON JSON parser object.
*
* @return a new BOON JSON parser object
*/
@Override
protected JsonParserAndMapper createObject() {
return new JsonParserFactory().useAnnotations().usePropertiesFirst().create();
}
}
And this is how you would use it:
// Create pool of 20 JSON Parser objects
JsonParserFixedPool jsonParserFixedPool = new JsonParserFixedPool(20);
// Borrow a JSON object from the pool
JsonParserAndMapper jsonParserAndMapper = jsonParserFixedPool.borrowObject();
// Return the object to the pool after using it
jsonParserFixedPool.returnObject(jsonParserAndMapper);
In this implementation the request to borrow an object will poll for 3000 milliseconds before throwing a DeletionException.
An alternative implementation would be to execute a monitor in a separate thread that attempts to maintain the pool size by creating/destroying objects in the pool.
Here is the code for the monitor:
public abstract class FlexibleObjectPool<T> extends AbstractObjectPool<T> {
private ScheduledExecutorService executorService;
protected FlexibleObjectPool() {
provokePoolMonitor(10, 20, 3000);
}
protected FlexibleObjectPool(int minIdle, int maxIdle, int validationInterval) {
provokePoolMonitor(minIdle, maxIdle, validationInterval);
}
/**
* Checks pool conditions in a separate thread.
* <p/>
* If pool size rises to above maxIdle then the number of objects
* deleted is equal to the excess over the minIdle.
* <p/>
* If pool size drops below minIdle new objects are created to
* bring it up to the minIdle level.
*
* @param minIdle minimum number of objects idling in the object pool
* @param maxIdle maximum number of objects idling in the object pool
* @param validationInterval time in milli-seconds for periodical checking of minIdle / maxIdle conditions in a separate thread.
* When the number of objects is less than minIdle, missing instances will be created.
* When the number of objects is greater than maxIdle, excess instances will be removed.
*/
protected void provokePoolMonitor(final int minIdle, final int maxIdle, int validationInterval) {
executorService = Executors.newSingleThreadScheduledExecutor();
executorService.scheduleWithFixedDelay(() -> {
int size = getPool().size();
if (size < minIdle) {
int sizeToBeAdded = minIdle - size;
for (int i = 0; i < sizeToBeAdded; i++) {
getPool().add(createObject());
}
} else if (size > maxIdle) {
int sizeToBeRemoved = size - maxIdle;
for (int i = 0; i < sizeToBeRemoved; i++) {
destroyObject(getPool());
}
}
}, validationInterval, validationInterval, TimeUnit.MILLISECONDS);
}
/**
* Shutdown this pool's executor service and deletes the object pool.
*/
public void destroyPool() {
if (executorService != null) {
executorService.shutdown();
executorService = null;
}
// Destroys the entire pool.
super.destroyPool();
}
}
And the implementation for a JSON pool:
public class JsonParserFlexiblePool extends FlexibleObjectPool<JsonParserAndMapper> {
private LinkedBlockingQueue<JsonParserAndMapper> pool = new LinkedBlockingQueue<>();
private int pollTimeout = 3000;
public JsonParserFlexiblePool() {
initialize(20, pool);
}
public JsonParserFlexiblePool(int poolSize) {
initialize(poolSize, pool);
}
public JsonParserFlexiblePool(int poolSize, int pollTimeout) {
initialize(poolSize, pool);
this.pollTimeout = pollTimeout;
}
public JsonParserFlexiblePool(int poolSize, int minIdle, int maxIdle, int validationInterval) {
super( minIdle, maxIdle, validationInterval);
initialize(poolSize, pool);
}
/**
* Borrow one BOON JSON parser object from the pool.
*
* @return returns one BOON JSON parser object
* @throws PoolDepletionException thrown if there are no BOON JSON parser objects in the pool
* @throws InterruptedException
*/
@Override
public JsonParserAndMapper borrowObject() throws PoolDepletionException, InterruptedException {
JsonParserAndMapper jsonParserAndMapper = pool.poll(this.pollTimeout, TimeUnit.MILLISECONDS);
if (jsonParserAndMapper == null) {
throw new PoolDepletionException("The JSON parser pool is empty and was not replenished within timeout limits.");
}
return jsonParserAndMapper;
}
/**
* Creates a BOON JSON parser object.
*
* @return a new BOON JSON parser object
*/
@Override
protected JsonParserAndMapper createObject() {
return new JsonParserFactory().useAnnotations().usePropertiesFirst().create();
}
}
And we use it like so:
// Create pool of 10 JSON Parser objects, with a min of 10 and max of 15 that executes the monitor every 2 seconds
jsonParserFlexiblePool = new JsonParserFlexiblePool(10, 10, 15, 2000);
// Borrow a JSON object from the pool
JsonParserAndMapper jsonParserAndMapper = jsonParserFixedPool.borrowObject();
// Return the object to the pool after using it
jsonParserFixedPool.returnObject(jsonParserAndMapper);