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;
}
}
Related examples in the same category