Object Resource Pooling

Object Resource Pooling

By Paul King, OCI Senior Software Engineer

March 2002


Introduction

The focus of this article is on how pooling resources can be used to gain efficiencies when using sharable objects, such as sockets, CORBA objects, database connections, files, and the like. The architecture of a simple, reusable pooling framework will be presented along with a sample implementation.

Today, many free pooling implementations are available, and major software vendors incorporate pooling capabilities in their products. In this article I attempt to distill and explain many of the common characteristics and behavior associated with a cache of objects in a resource pool.

Motivation

If the overhead associated with creating a sharable resource is expensive, that resource is a good candidate for pooling. Rather than take the performance hit associated with creating an object each time it needs to be used, the idea behind pooling is to create the resource in advance and store it away so it can be reused over-and-over.

Even if the overhead associated with creating a resource on the fly is low or acceptable, pooling may be necessary if a limited number of resources are available and sharing is required.

Also, pooling implementations can apply strategies that govern run-time behavior, such as load balancing, all-resources-busy situations, and other policies to optimize resource utilization.

Architecture of a Simple Pool Framework

A simple pooling framework can be depicted as follows:

Simple Pool Framework

In this framework ConcretePooledObjects extend the java.lang.Object class. This allows objects of any class to be pooled.

Some pooling implementations use a marker interface that pooled objects must implement. If that is the case, in order to pool classes that do not implement the marker interface, a Decorator class needs to be created that implements the marker interface. The Decorator class would then wrap the class that does not implement the marker interface.

The Pool interface defines the methods to get a shared object from the pool and release it back to the pool. These paired counter-part methods are often implemented using other names, such as get/put, fetch/release, etc.

The AbstractPool class implementation is shown below. Here we define the implementation for the get and release methods and use ArrayLists to store pooled objects that are free and in-use.

To guarantee mutually exclusive access and updates to the collection of objects, the get and release methods are synchronized. It is interesting to note that these methods can't be designated synchronized in the Pool interface. This is because methods declared in Java interfaces cannot be qualified as synchronized.

When creating objects to be added to the collection, the AbstractPool uses the PooledObjectFactory it is told to use (for example, it can determine the factory from a property). The ConcretePooledObjectFactory is ultimately responsible for creating ConcretePooledObjects.

  1. import java.util.ArrayList;
  2.  
  3. public abstract class AbstractPool implements Pool {
  4.  
  5. /** Property name that specifies our PooledObjectFactory */
  6. public static final String POOLED_OBJECT_FACTORY_CLASS_NAME =
  7. "pooledObjectFactoryClassName";
  8.  
  9. private int defaultPoolSize = 2;
  10.  
  11. // Pooled objects available and in use.
  12. private ArrayList freePooledObjects = new ArrayList();
  13. private ArrayList inusePooledObjects = new ArrayList();
  14.  
  15. // Our instance of the POOLED_OBJECT_FACTORY_CLASS_NAME
  16. private PooledObjectFactory pooledObjectFactory;
  17.  
  18. public AbstractPool () {
  19. // Fetch the Property name that specifies the
  20. // PooledObjectFactory to use...
  21. String pooledObjectFactoryClassName =
  22. System.getProperty(POOLED_OBJECT_FACTORY_CLASS_NAME);
  23.  
  24. try {
  25. pooledObjectFactory = (PooledObjectFactory)
  26. Class.forName(pooledObjectFactoryClassName).newInstance();
  27. } catch (Exception ex) {
  28. // Ouch
  29. }
  30.  
  31. // Build the pool...
  32. for (int i = 0; i < defaultPoolSize; i++) {
  33. freePooledObjects.add(pooledObjectFactory.newInstance());
  34. }
  35. }
  36.  
  37. public synchronized Object getPooledObject() {
  38. if (freePooledObjects.size() == 0) {
  39. // Sorry, none available...
  40. return null;
  41. }
  42.  
  43. // Remove the first...
  44. Object pooledObject = freePooledObjects.remove(0);
  45.  
  46. // Add it to the in-use list
  47. inusePooledObjects.add(pooledObject);
  48.  
  49. return pooledObject;
  50. }
  51.  
  52. public synchronized void releasePooledObject(Object pooledObject) {
  53. freePooledObjects.add(inusePooledObjects.remove(
  54. inusePooledObjects.indexOf(pooledObject)));
  55. }
  56. }

Pooling Policies

Pooling implementations often need to enforce policies that dictate run-time behavior. Such policies include:

Load Balancing
Pooled objects can be handed out using an optimization strategy, such as round-robin or least-used, or use some other selection scheme.
Low-water/High-water
A low-water mark is the minimum number of shared objects that will be instantiated when the pool is created. If at any time, all of these objects are in use, a subsequent get request for an object will result in the pool using the associated PooledObjectFactory to create an additional pooled object. This process is allowed until the pool contains the maximum (high-water) number of objects.
Blocking vs. No-Wait
If no objects in a pool are available, a blocking policy allows a client to wait for a specific amount of time hoping that it can claim an object when it is returned to the pool. If the timeout period expires, typically a failure exception is reported back to the client. A no-wait policy immediately reports a failure back to the client if no objects in a pool are available.
Failed resource recovery
If a pooled object becomes unusable due to some type of failure, such as disk space exhausted or network problems, then a recovery policy dictates what should be done to fix the object.

For performance reasons, it may not be practical to try to synchronously recover a failed object at the point where the failure was detected. Some strategies simply mark the object as failed, effectively removing it from the pool. A monitor thread can then poll for failed objects and attempt to fix them, allowing recovery to occur asynchronously. This eliminates the need to attempt failure recovery at the point in time where the problem was detected.

Monitor threads can also proactively perform diagnostic checks on non-failed objects in the pool on a periodic basis (for example, if the object has not been successfully used within some period of time) and fix them if errant conditions are detected.
Recycler idiom
JavaWorld Java Tip 78: Recycle broken objects in resource pools focused on a particular approach to reuse broken objects.

A Recycler is a form of the Director object that appears in the Builder design pattern. When a failed object is detected, a Recycler examines the object and to tries to replace only the part (or parts) of the object that is broken. This alleviates the cost of removing and creating new pooled objects from scratch. If the Recycler is successful in fixing the failed object, it returns it to the pool.

Concrete Implementation

The following sample code implements a simple pool that supports a blocking policy such that if no objects in the pool are available, then getPooledObject() will wait until an object becomes available. With little effort, such a blocking policy could be implemented in the AbstractPool class. However, for the purposes of this example, we will implement blocking in this concrete class.

The getPooledObject() method attempts to retrieve the next pooled object by calling that method on the super-class, AbstractPool. If no pooled object is available, then the super-class getPooledObject() method returns null.

In this case, we execute wait(). The no-arg version of wait is called here for simplicity. This can result in an infinite wait. Industrial strength implementations would allow a timeout to be specified, which if expired, would result in an exception being thrown.

The call to wait() releases the synchronized lock on this instance of the SocketPool object. This will allow a subsequent call to releasePooledObject() to proceed without blocking. The call to releasePooledObject() calls notifyAll(), which will cause the invocation of wait() to complete in any associated thread(s). Then we recursively call our self again, which should succeed in obtaining a free object if contention is weak.

  1. class BlockingPool extends AbstractPool {
  2.  
  3. public synchronized Object getPooledObject() {
  4. // Try to aquire. Null return means no objects available.
  5. Object pooledObject = super.getPooledObject();
  6.  
  7. if (pooledObject == null) {
  8. try {
  9. // Wait until notifyAll is called...
  10. wait();
  11. } catch (InterruptedException ex) {
  12. // Ouch
  13. }
  14.  
  15. // We received notification that a socket was released.
  16. return getPooledObject(); // Try again
  17. }
  18. return pooledObject;
  19. }
  20.  
  21. public synchronized void releasePooledObject(Object pooledObject) {
  22. super.releasePooledObject(pooledObject);
  23. notifyAll();
  24. }
  25. }

To tie all of this together and show the ease of use, we present a simple client class that utilizes our pooling framework.

The client intends to pool sockets. As such, we need to create a SocketPooledObjectFactory that simply returns a java.net.Socket instance from its newInstance() method, following the Factory Method design pattern.

  1. import java.net.Socket;
  2.  
  3. class SocketPooledObjectFactory implements PooledObjectFactory {
  4.  
  5. public Object newInstance() {
  6. return new Socket(); // Use the simple constructor for this example
  7. }
  8. }

Lastly, we present the simple client class that utilizes our pooling framework.

  1. import java.net.Socket;
  2.  
  3. class SocketPoolClient {
  4.  
  5. public static void main(String args[]) {
  6. // Set the factory...
  7. System.setProperty(AbstractPool.POOLED_OBJECT_FACTORY_CLASS_NAME,
  8. "SocketPooledObjectFactory");
  9.  
  10. // Create our pool...
  11. BlockingPool socketPool = new BlockingPool();
  12.  
  13. Socket socketPooledObject = null;
  14.  
  15. try {
  16. // Get a SocketPooledObject from the pool. Note the need
  17. // to cast from a PooledObject to a SocketPooledObject.
  18. socketPooledObject = (Socket) socketPool.getPooledObject();
  19. System.out.println("getPooledObject was a success!");
  20. } finally {
  21. // Release if we were successful in our get...
  22. if (socketPooledObject != null) {
  23. socketPool.releasePooledObject(socketPooledObject);
  24. System.out.println("releasePooledObject was a success!");
  25. }
  26. }
  27. }
  28. }

Once finished using the Socket, it is the client's obligation and responsibility to call releasePooledObject().

If for some reason the client does not call releasePooledObject(), objects will be leaked from the pool and cause problems. Thus, to be sure that we return the Socket back to the pool, even in the event of an unhandled exception, we place the call to releasePooledObject() within a finally block.

From a defensive coding standpoint, the use of finally blocks is a good idea. For a detailed discussion of finally, please refer to the May 2000 SETT article, "An Integral Part of Exception Handling, Finally."

Freely Available Implementations

Among many freely available implementations available, here are a few that I have found useful and interesting.

Summary

Industrial-strength applications need to consider pooling shared resources in order to gain efficiencies. Before implementing your own home-grown framework such as that shown in this article, be sure to see if the package or utility associated with the resource you want to pool already provides a means to facilitate this functionality. You may be surprised, as this behavior is increasingly becoming the norm.

References



Software Engineering Tech Trends (SETT) is a regular publication featuring emerging trends in software engineering.