Java tutorial
/* * 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; } }