Mega Code Archive

 
Categories / Java / Development Class
 

A pool of objects that should only be used by one thread at a time

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