2
\$\begingroup\$

This question is in response to the code review here: Simple lightweight object pool. Please visit the original question for background.

I have taken on board all the suggestions made and carefully amended the code in response. As the answer is in two parts: one being the code review and that other being suggestions, I will respond to each separately. Additionally, I have added the code to Github code review changes and suggestions.

  1. I have made the interface define the pool contract and removed the @FunctionalInterface as it served no purpose.
  2. The abstract class constructs the pool using only constructors and it has a private method that performs the initialization.
  3. The pool object is now encapsulated and properties of the pool such as its size are accessed via methods in the abstract class.
  4. The method borrowObject() is now named acquire() and returnObject() has been renamed surrender().
  5. The pool now has a life-cycle status of STARTING, STARTED, STOPPING or STOPPED. This prevents access to the pool while it is being destroyed/has been destroyed. The destroy() method removes an item from the queue. This is used by the monitor to maintain the pool size when it grows beyond maxIdle. Also it is used by destroyPool() to remove objects from the pool.
  6. It is no longer possible to specify the queue implementation. The pool uses the BlockingQueue implementation. This prevents the client using a non-thread safe implementation.
  7. The shadowed pool variable has been removed.

Questions:

  1. You mentioned the use of Closable. Can you elaborate, please?
  2. I have removed the need to make the queue access thread-safe as the BlockingQueue takes care of it internally. However I would like to know how I would implement thread safety. This is an area that I would like to improve my knowledge of.
  3. I have implemented a pool life-cycle status. Has this been done well?

Here is the changed code:

public interface Pool<T> {
    T acquire() throws Exception;
    void surrender(T item) throws Exception;
}

The implementation of the interface:

public abstract class AbstractObjectPool<T> implements Pool<T> {

public enum PoolState {
    STARTING, STARTED, STOPPING, STOPPED
}

private PoolState currentStatus;
private BlockingQueue<T> pool = new LinkedBlockingQueue<>();
private int pollTimeout;
private int poolSize;

protected AbstractObjectPool() {
    this.poolSize = 100;
    initialize();
}

protected AbstractObjectPool(int poolSize) {
    this.poolSize = poolSize;
    initialize();
}

protected AbstractObjectPool(int poolSize, int pollTimeout) {
    this.pollTimeout = pollTimeout;
    this.poolSize = poolSize;
    initialize();
}


/**
 * Initialise the pool and populate it with poolSize number of objects
 */
private void initialize() {
    updatePoolStatus(PoolState.STARTING);
    if (poolSize < 1) {
        throw new IllegalArgumentException("Pool Size must be at least 1");
    }
    while (pool.size() < poolSize) {
        pool.add(createObject());
    }
    updatePoolStatus(PoolState.STARTED);
}

/**
 * Sets the pools current status
 *
 * @param currentStatus the pool's current status
 */
private void updatePoolStatus(PoolState currentStatus) {
    this.currentStatus = currentStatus;
}


/**
 * Returns the current pool size
 *
 * @return number of objects in the pool
 */
public int getCurrentPoolSize() {
    return pool.size();
}

/**
 * Is the pool null
 *
 * @return true if null otherwise false
 */
public boolean isPoolNull() {
    return pool == null;
}


/**
 * Returns the status of the current pool.
 *
 * @return the current pool status
 */
public PoolState poolState() {
    return this.currentStatus;
}


/**
 * 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 T acquire() throws Exception {
    checkPoolStatus();
    T t = pool.poll(pollTimeout, TimeUnit.MILLISECONDS);

    if (t == null) {
        throw new PoolDepletionException("The pool is empty and was not replenished within timeout limits.");
    }

    return t;
}


/**
 * 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 surrender(T object) throws Exception {
    checkPoolStatus();
    if (object == null) {
        return;
    }
    this.pool.offer(object);
}


/**
 * Creates a new object.
 *
 * @return T new object
 */
protected abstract T createObject();


/**
 * Adds object to the pool.
 *
 * @return true if added successfully otherwise false
 */
public boolean add() throws Exception {
    checkPoolStatus();
    return pool.add(createObject());
}


/**
 * Checks that the pool is running and ready for use otherwise it throws an exception
 *
 * @throws Exception thrown if pool not ready or shutting down
 */
private void checkPoolStatus() throws Exception {
    if (poolState() != PoolState.STARTED) throw new Exception("Pool not ready or stopped");
}

/**
 * Destroys an object.
 */
public void destroy() {
    pool.poll();
}


/**
 * Destroys the entire pool.
 */
public void destroyPool() {
    updatePoolStatus(PoolState.STOPPING);
    while (pool != null && !pool.isEmpty()) {
        destroy();
    }
    pool = null;
    updatePoolStatus(PoolState.STOPPED);
}

}

An example of an implementation of a fixed pool:

public class JsonParserFixedPool extends AbstractObjectPool<JsonParserAndMapper> {

public JsonParserFixedPool(int poolSize, int poolTimeout) {
    super(poolSize, poolTimeout);
}

public JsonParserFixedPool(int poolSize) {
    super(poolSize);
}

/**
 * Creates a BOON JSON parser object.
 *
 * @return a new BOON JSON parser object
 */
@Override
protected JsonParserAndMapper createObject() {
    return new JsonParserFactory().useAnnotations().usePropertiesFirst().create();
}

}

An example of usage:

jsonParserFixedPool = new JsonParserFixedPool(10);
JsonParserAndMapper jsonParserAndMapper = jsonParserFixedPool.acquire();
jsonParserFixedPool.surrender(jsonParserAndMapper);

Now to the suggestions:

  1. The constructor now accepts a Supplier that provides a new object.
  2. I decided to keep the monitor. I like to give the client the option to have a pool with or without a monitor by choosing which abstract class to implement. I also felt that this logic should be separate from the acquire() method, trying to follow separation of concerns principle.
  3. I have added a default handleDepletion() method. This allows the client to implement their own logic for handling pool depletion.
  4. I decided not to include the lazyAdd() method. I felt that the monitor functionality and the handleDepletion() method take care of pool replenishment, although I like the idea of using a thread to create a new object.

Questions:

You mention "...actually implement the Supplier interface yourself". Do you mean something like this? The abstract class implements the Supplier interface and the concrete class implements the get() method like this:

public abstract class AbstractObjectPool<T> implements Pool<T>, Supplier<T> { ... }

@Override
public JsonParserAndMapper get() {
    return new JsonParserFactory().useAnnotations().usePropertiesFirst().create();
}

Now for the code based on the suggestions:

Same interface as above and the following abstract class.

public abstract class AbstractObjectPool<T> implements Pool<T> {

public enum PoolState {
    STARTING, STARTED, STOPPING, STOPPED
}

private PoolState currentStatus;
private BlockingQueue<T> pool= new LinkedBlockingQueue<>();
private int pollTimeout;
private final int poolSize;
private final Supplier<T> supplier;

protected AbstractObjectPool(Supplier<T> supplier) {
    this.supplier = supplier;
    this.poolSize = 100;
    initialize();
}

protected AbstractObjectPool(Supplier<T> supplier, int poolSize) {
    this.supplier = supplier;
    this.poolSize = poolSize;
    initialize();
}

protected AbstractObjectPool(Supplier<T> supplier, int poolSize, int pollTimeout) {
    this.supplier = supplier;
    this.pollTimeout = pollTimeout;
    this.poolSize = poolSize;
    initialize();
}


/**
 * Initialise the pool and populate it with poolSize number of objects
 */
private void initialize() {
    updatePoolStatus(PoolState.STARTING);
    if (poolSize < 1) {
        throw new IllegalArgumentException("Pool Size must be at least 1");
    }
    while (pool.size() < poolSize) {
        pool.add(supplier.get());
    }
    updatePoolStatus(PoolState.STARTED);
}

/**
 * Sets the pools current status
 *
 * @param currentStatus the pool's current status
 */
private void updatePoolStatus(PoolState currentStatus) {
    this.currentStatus = currentStatus;
}


/**
 * Returns the current pool size
 *
 * @return number of objects in the pool
 */
public final int getCurrentPoolSize() {
    return pool.size();
}

/**
 * Is the pool null
 *
 * @return true if null otherwise false
 */
public final boolean isPoolNull() {
    return pool == null;
}


/**
 * Returns the status of the current pool.
 *
 * @return the current pool status
 */
public final PoolState poolState() {
    return this.currentStatus;
}


/**
 * 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 final T acquire() throws Exception {
    checkPoolStatus();
    T t = pool.poll(pollTimeout, TimeUnit.MILLISECONDS);

    if (t == null) {
        return handleDepletion(supplier);
    }

    return t;
}


/**
 * 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 final void surrender(T object) throws Exception {
    checkPoolStatus();
    if (object == null) {
        return;
    }
    this.pool.offer(object);
}


/**
 * Adds object to the pool.
 *
 * @return true if added successfully otherwise false
 */
public final boolean add() throws Exception {
    checkPoolStatus();
    return pool.add(supplier.get());
}

/**
 * What to do if there's nothing to return within the expected time. options
 * are to create an instance manually, or alternatively throw an exception.
 * returning null would be a bad idea, though.
 *
 * @param supplier
 *            the supplier that feeds this pool - can be used to get a
 *            guaranteed, but slow instance.
 * @return the value for the client to use
 * @throws PoolDepletionException
 *             if that is how the pool instance should respond.
 */
protected T handleDepletion(Supplier<T> supplier) throws PoolDepletionException{
    throw new PoolDepletionException("The pool is empty and was not replenished within timeout limits.");
}

/**
 * Checks that the pool is running and ready for use otherwise it throws an exception
 *
 * @throws Exception thrown if pool not ready or shutting down
 */
private void checkPoolStatus() throws Exception {
    if (poolState() != PoolState.STARTED) throw new Exception("Pool not ready or stopped");
}

/**
 * Destroys an object.
 */
public void destroy() {
   pool.poll();
}


/**
 * Destroys the entire pool.
 */
public void destroyPool() {
    updatePoolStatus(PoolState.STOPPING);
    while (pool != null && !pool.isEmpty()) {
        destroy();
    }
    pool = null;
    updatePoolStatus(PoolState.STOPPED);
}

}

An extension of the AbstractObjectPool:

public abstract class FlexibleObjectPool<T> extends AbstractObjectPool<T> {

private ScheduledExecutorService executorService;

private float poolSizeFactor = 0.8f;

/**
 * Construct a flexible pool with default configuration values.
 *
 * @param supplier the supplier that creates a new instance of the object
 * @param poolSize the initial size of the pool
 */
public FlexibleObjectPool(Supplier<T> supplier, int poolSize) {
    super(supplier, poolSize);
    int minIdle = (int) (poolSize * poolSizeFactor);
    int maxIdle = (int) (poolSize * (1 + (1 - poolSizeFactor)));
    provokePoolMonitor(minIdle, maxIdle, 3000);
}

/**
 * Construct a flexible pool.
 * <p/>
 * Set starting pool size equal to the average of the minIdle and maxIdle.
 *
 * @param supplier           the supplier that creates a new instance of the object
 * @param minIdle            the minimum pool size that the monitor should attempt to maintain
 * @param maxIdle            the maximum pool size that the monitor should attempt to maintain
 * @param validationInterval the interval at which to fire the monitor
 */
public FlexibleObjectPool(Supplier<T> supplier, int minIdle, int maxIdle, int validationInterval) {
    super(supplier, (maxIdle + minIdle) / 2);
    provokePoolMonitor(minIdle, maxIdle, validationInterval);
}

public FlexibleObjectPool(Supplier<T> supplier, FlexiblePoolConfig config) {
    super(supplier, (config.maxIdle + config.minIdle) / 2);
    provokePoolMonitor(config.minIdle, config.maxIdle, config.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) {
    if (minIdle < 1 || minIdle > maxIdle) {
        throw new IllegalArgumentException("minIdle must be at least 1, and at least equal to maxIdle");
    }
    executorService = Executors.newSingleThreadScheduledExecutor();
    executorService.scheduleWithFixedDelay(() -> {
        int size = getCurrentPoolSize();
        if (size < minIdle) {
            int sizeToBeAdded = minIdle - size;
            for (int i = 0; i < sizeToBeAdded; i++) {
                try {
                    add();
                } catch (Exception e) {
                    break;
                }
            }
        } else if (size > maxIdle) {
            int sizeToBeRemoved = size - maxIdle;
            for (int i = 0; i < sizeToBeRemoved; i++) {
                destroy();
            }
        }

    }, 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();
}


public class FlexiblePoolConfig {

    protected final int minIdle;
    protected final int maxIdle;
    protected final int validationInterval;

    public FlexiblePoolConfig(final int minIdle, final int maxIdle, final int validationInterval) {
        this.minIdle = minIdle;
        this.maxIdle = maxIdle;
        this.validationInterval = validationInterval;
    }

}
}

An example of a concrete implementation:

public class JsonParserFlexiblePool extends FlexibleObjectPool<JsonParserAndMapper> {

private static Supplier<JsonParserAndMapper> createJSONParserAndMapper() {
    return () -> new JsonParserFactory().useAnnotations().usePropertiesFirst().create();
}

public JsonParserFlexiblePool() {
    super(createJSONParserAndMapper(), 20);
}

public JsonParserFlexiblePool(int poolSize) {
    super(createJSONParserAndMapper(), poolSize);
}

public JsonParserFlexiblePool(int poolSize, int pollTimeout) {
    super(createJSONParserAndMapper(), poolSize, pollTimeout, 0);
}

public JsonParserFlexiblePool(int minIdle, int maxIdle, int validationInterval) {
    super(createJSONParserAndMapper(), minIdle, maxIdle, validationInterval);
}

public JsonParserFlexiblePool(FlexiblePoolConfig flexiblePoolConfig) {
    super(createJSONParserAndMapper(), flexiblePoolConfig);
}

}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.