ResourcePool.java Source code

Java tutorial

Introduction

Here is the source code for ResourcePool.java

Source

/*
 * ResourcePool.java Created Oct 21, 2010 by Andrew Butler, PSL
 */
//package prisms.util;

/**
 * A pool of objects that should only be used by one thread at a time
 * 
 * @param <T>
 *            The type of resource that is held by this pool
 */
public class ResourcePool<T> {
    /**
     * A ResourceCreationException may be thrown by a
     * {@link ResourcePool.ResourceCreator} if it is unable to create a new
     * resource
     */
    public static class ResourceCreationException extends Exception {
        /**
         * @param message
         *            The message detailing why the resource cannot be created
         */
        public ResourceCreationException(String message) {
            super(message);
        }

        /**
         * @param message
         *            The message detailing why the resource cannot be created
         * @param cause
         *            The throwable that is the cause of the inability to create
         *            the resource
         */
        public ResourceCreationException(String message, Throwable cause) {
            super(message, cause);
        }
    }

    /**
     * Creates and destroys resources on demand for the pool
     * 
     * @param <T>
     *            The type of value to create and destroy
     */
    public interface ResourceCreator<T> {
        /**
         * Creates a new resource to be available to the pool
         * 
         * @return The new resource
         * @throws ResourceCreationException
         *             If the new resource cannot be created for any reason
         */
        T createResource() throws ResourceCreationException;

        /**
         * Takes care of releasing a resource's system resources when it is no
         * longer needed by the pool. This will only be needed if the pool's
         * maximum size is reduced below the number of resources being managed
         * by the pool.
         * 
         * @param resource
         *            The resource that is no longer being used by the pool
         */
        void destroyResource(T resource);
    }

    private final java.util.concurrent.locks.ReentrantLock theLock;

    private final java.util.ArrayList<T> theAvailableResources;

    private final java.util.ArrayList<T> theInUseResources;

    private final java.util.HashSet<T> theRemovedResources;

    private final java.util.LinkedList<Thread> theWaitingThreads;

    private final ResourceCreator<T> theCreator;

    private int theMaxSize;

    private volatile boolean isClosed;

    /**
     * Creates an empty pool that must be supplied with data via the
     * {@link #addResource(Object)} method
     */
    public ResourcePool() {
        this(null, 0);
    }

    /**
     * Creates an empty pool that is capable of creating its own resources on
     * demand
     * 
     * @param creator
     *            The creator capable of creating resources and capable of
     *            destroying them
     * @param maxSize
     *            The maximum size for the pool
     */
    public ResourcePool(ResourceCreator<T> creator, int maxSize) {
        theLock = new java.util.concurrent.locks.ReentrantLock();
        theAvailableResources = new java.util.ArrayList<T>();
        theInUseResources = new java.util.ArrayList<T>();
        theRemovedResources = new java.util.HashSet<T>();
        theWaitingThreads = new java.util.LinkedList<Thread>();
        theCreator = creator;
        theMaxSize = maxSize;
    }

    /**
     * @return The maximum size of this pool
     * @see #setMaxSize(int)
     */
    public int getMaxSize() {
        return theMaxSize;
    }

    /**
     * Sets the maximum number of resources for this pool. This affects when new
     * resources will be created internally by the resource pool using the
     * creator passed to the {@link #ResourcePool(ResourceCreator, int)}
     * constructor. The parameter does not limit the number of resources that
     * may be given to the pool using the {@link #addResource(Object)} method.
     * 
     * If the maximum size of a pool is reduced, some resources may be destroyed
     * (via {@link ResourceCreator#destroyResource(Object)}).
     * 
     * If this resource pool was created without a creator, this attribute has
     * no effect.
     * 
     * @param size
     *            The maximum size of the pool
     */
    public void setMaxSize(int size) {
        theMaxSize = size;
    }

    /** @return Whether this resource pool has been marked as closed */
    public boolean isClosed() {
        return isClosed;
    }

    /**
     * Clears this resource pool. All available resources will be removed (and
     * destroyed if there is a creator). In-use resources will be removed and
     * destroyed when they are released.
     */
    public void close() {
        isClosed = true;
        theLock.lock();
        try {
            java.util.Iterator<T> iter;
            iter = theAvailableResources.iterator();
            while (iter.hasNext()) {
                T res = iter.next();
                if (theCreator != null)
                    theCreator.destroyResource(res);
                iter.remove();
            }
        } finally {
            theLock.unlock();
        }
    }

    /** @return The total number of resources managed by this pool */
    public int getResourceCount() {
        return theAvailableResources.size() + theInUseResources.size();
    }

    /** @return The number of resources in this pool available for use */
    public int getAvailableResourceCount() {
        return theAvailableResources.size();
    }

    /** @return The number of resources in this pool currently being used */
    public int getInUseResourceCount() {
        return theInUseResources.size();
    }

    /**
     * Gets a resource from the pool. The resource will be marked as in-use and
     * will never be given to another invocation of this method nor destroyed by
     * the creator until the resource is released. For this reason, it is
     * advisable to use a try/finally structure to ensure that the resource is
     * released when its use is finished.
     * 
     * In the case that a resource is not currently available (i.e. if all
     * available resources are in use and either there is no creator or the
     * maximum size has been reached), if the wait parameter is true, this
     * method will block until a resource is available. If the wait parameter is
     * false, null will be returned.
     * 
     * @param wait
     *            Whether to wait for the resource rather than accept null if no
     *            resources or available
     * @return The free resource to use, or null if the wait parameter is false
     *         and there are no free resources
     * @throws ResourceCreationException
     *             If an error occurs when the creator creates a new resource
     */
    public T getResource(final boolean wait) throws ResourceCreationException {
        if (isClosed)
            throw new IllegalStateException("This resource pool is closed");
        T ret = null;
        boolean waiting = false;
        do {
            try {
                theLock.lock();
                try {
                    if (theAvailableResources.size() == 0)
                        updateResourceSet();
                    if (theAvailableResources.size() > 0) {
                        if (waiting)
                            theWaitingThreads.remove(Thread.currentThread());
                        waiting = false;
                        ret = theAvailableResources.remove(theAvailableResources.size() - 1);
                        theInUseResources.add(ret);
                    } else if (wait && !waiting) {
                        waiting = true;
                        theWaitingThreads.add(Thread.currentThread());
                    }
                } finally {
                    theLock.unlock();
                }
                if (waiting) {
                    try {
                        Thread.sleep(24L * 60 * 60 * 1000);
                    } catch (InterruptedException e) {
                    }
                }
            } catch (Exception e) {
                if (e instanceof InterruptedException) {
                } else if (e instanceof RuntimeException)
                    throw (RuntimeException) e;
                else
                    throw new IllegalStateException("Should not be able to get here", e);
            }
        } while (waiting);
        return ret;
    }

    /**
     * Releases a used resource back into the pool for use
     * 
     * @param resource
     *            The resource to release to the pool
     */
    public void releaseResource(T resource) {
        theLock.lock();
        try {
            theInUseResources.remove(resource);
            if (!theRemovedResources.remove(resource)) {
                if (isClosed) {
                    if (theCreator != null)
                        theCreator.destroyResource(resource);
                } else if (theCreator != null && getResourceCount() >= theMaxSize)
                    theCreator.destroyResource(resource);
                else
                    addResource(resource);
            }
        } finally {
            theLock.unlock();
        }
    }

    /**
     * Adds a resource to this set. After the resource is added, it is treated
     * identically to any other resource in this pool. In particular, if this
     * resource pool is managed by a creator, the new resource may be destroyed
     * by the pool if the pool's maximum size is reduced to make the pool too
     * large.
     * 
     * @param resource
     *            The resource to make available to the pool
     */
    public void addResource(T resource) {
        if (isClosed)
            throw new IllegalStateException("This resource pool is closed");
        Thread waiting = null;
        theLock.lock();
        try {
            if (!theWaitingThreads.isEmpty() || !isClosed) {
                theAvailableResources.add(resource);
                if (!theWaitingThreads.isEmpty())
                    waiting = theWaitingThreads.removeFirst();
            }
        } finally {
            theLock.unlock();
        }
        if (waiting != null)
            waiting.interrupt();
    }

    /**
     * Removes a resource from this pool. If the resource no longer exists in
     * the pool, false is returned. If the resource is currently available, it
     * will be removed from the pool completely and false will be returned.
     * Otherwise, the resource is marked for removal and will not be returned to
     * the available resource set (nor will it be destroyed by the creator) when
     * it is released and true will be returned.
     * 
     * @param resource
     *            The resource to remove from the pool
     * @return Whether the resource is still being used
     */
    public boolean removeResource(T resource) {
        theLock.lock();
        try {
            if (theAvailableResources.remove(resource))
                return false;
            if (!theInUseResources.contains(resource))
                return false;
            if (!theRemovedResources.contains(resource))
                theRemovedResources.add(resource);
            return true;
        } finally {
            theLock.unlock();
        }
    }

    private void updateResourceSet() throws ResourceCreationException {
        if (theCreator == null)
            return;
        theLock.lock();
        try {
            int newRC = getNewResourceCount();
            int total = getResourceCount();
            if (newRC < total) { // Need to kill some threads
                int killCount = total - newRC;
                for (; theAvailableResources.size() > 0 && killCount > 0; killCount--) {
                    T res = theAvailableResources.remove(theAvailableResources.size() - 1);
                    if (theCreator != null)
                        theCreator.destroyResource(res);
                }
            } else if (newRC > total) {
                int spawnCount = newRC - total;
                for (int t = 0; t < spawnCount; t++)
                    theAvailableResources.add(0, theCreator.createResource());
            }
        } finally {
            theLock.unlock();
        }
    }

    private int getNewResourceCount() {
        int used = getInUseResourceCount();
        int total = getResourceCount();
        int ret;
        if (used == total) {
            for (ret = 1; ret * ret <= total; ret++)
                ;
            ret = ret * ret;
        } else {
            int ceilUsedSqrt;
            for (ceilUsedSqrt = 1; ceilUsedSqrt * ceilUsedSqrt < used; ceilUsedSqrt++)
                ;
            int floorTotalSqrt;
            for (floorTotalSqrt = 1; floorTotalSqrt * floorTotalSqrt <= total; floorTotalSqrt++)
                ;
            floorTotalSqrt--;
            if (ceilUsedSqrt < floorTotalSqrt - 1)
                ret = (ceilUsedSqrt + 1) * (ceilUsedSqrt + 1);
            else
                ret = total;
        }
        if (ret > theMaxSize)
            ret = theMaxSize;
        return ret;
    }
}