The Creation Thread Pools: An Extended Example

} _currentPosition = startingSize -1; } }

12.3.4 Problems with SimplePool

SimplePool has a number of nice attributes. The first is that, as advertised, the code is straightforward. Another is threadsafety. Since both getObject and returnObject are synchronized, SimplePool is threadsafe. Moreover, because only one thread ever gets to _helper at a time, SimplePool is threadsafe independent of the implementation of PoolHelper . However, SimplePool also has four major problems. First, there is no effective bound on the number of pooled objects that are created. If all the pooled objects are checked out, then another object is created. This can be unfortunate. When our server is really busy, it may spend an inordinate amount of time and resources creating pooled objects that it will then throw away. In the case of database connections, it may swamp the database server as well. The second problem is that getObject and returnObject are both synchronized. Thus, at most, one thread can either get or return an object. This, in turn, has two effects. First, returning an object to the pool, an operation that ought to be quite fast for the client threads, may take a long time. Second, its quite possible that instances of SimplePool will not achieve efficient reuse during peak usage periods. Suppose, for example, that all the pooled objects have been vended, and 15 more requests for objects come in. Its possible that 15 new objects will be created, even if during that same time period, 11 client threads were attempting to return pooled objects. The third major problem with SimplePool is that the validity checks, and the destruction of surplus pooled objects, are done in the calling thread. One of the big benefits of using a pool is that it makes the client threads more responsive by offloading most of their work to the pool. Wed certainly like to move validation and destruction out of the client threads as well. The last major defect with SimplePool is that the pool doesnt shrink during quiet times. Once the pool is at maximum size, it will keep all of its allocated objects forever. Given that pooled objects are usually scarce resources, wed probably like a way to gradually shrink the pool when its not often being used and release those scare resources for other uses. Each of these problems cries out for background threads inside our pooling mechanism. In particular, we will implement three threads: one that handles object creation, another that handles returning objects to the pool, and a third that handles shrinking the pool over time.

12.3.5 The Creation Thread

The first step in solving SimplePool s problems is to add a separate thread to handle object creation. This will enable us to solve the first two problems mentioned earlier. The basic idea is: • A separate thread handles object-creation requests. It does so one at a time. • When calls to getObject are made, the pool first checks its local storage. If no object is available, it sends a message to the object-creation thread, requesting that a single object be created, and then waits. • When the object-creation thread receives the message, it creates a single object if the pool hasnt exceeded its maximum size limit and notifies a waiting thread. If the pool is already at its maximum size, the object-creation thread does nothing. • When objects are checked into the pool, notification also occurs. This accomplishes two things. First, it handles the problems caused by the object-creation threads refusal to create objects when the pool is already at maximum size. Second, it increases the likelihood that waiting creation requests will be able to reuse objects that were checked in while they were waiting. The code to implement this version of Pool isnt much longer than that for SimplePool . But its much trickier. Avoiding a deadlock between the threads calling getObject and the object creation thread requires some thought. In addition, making sure that all the getObject calls are eventually satisfied also requires some work. Heres the code for ThreadedPool1 : public class ThreadedPool1 implements Pool { private int _maximumSize; private Vector _availableObjects; private int _totalNumberOfObjects; private PoolHelper _helper; private ObjectCreator _creator; public ThreadedPool1String poolName, int maximumSize, PoolHelper helper { _maximumSize = maximumSize; _helper = helper; _availableObjects = new Vector ; startCreatorThreadpoolName; } public Object getObject { Object returnValue = null; while null== returnValue = getLocallyAvailableObject { _creator.askForObject ; waitForAvailableObject ; } return returnValue; } public void returnObjectObject object { if _helper.isObjectStillValidobject { _availableObjects.addobject; notifyWaitingGets ; } else { _helper.disposeobject; _totalNumberOfObjects--; } return; } protected void createAndAddObject { Object createdObject = null; if _totalNumberOfObjects _maximumSize { Object newObject = _helper.create ; _availableObjects.addnewObject; _totalNumberOfObjects++; } if null=createdObject { notifyWaitingGets ; } return; } private synchronized void waitForAvailableObject { try { wait ; } catch InterruptedException ignored {} } private synchronized void notifyWaitingGets { notify ; } private Object getLocallyAvailableObject { Locks the container while we check for values. That way, we dont simultaneously vend the same object to two different requests synchronized_availableObjects { if _availableObjects.isEmpty { int lastPosition = _availableObjects.size -1; _return _availableObjects.removelastPosition; } return null; } private void startCreatorThreadString poolName { _creator = new ObjectCreatorthis; Thread creatorThread = new Thread_creator, poolName + creation thread; creatorThread.setPriorityThread.NORM_PRIORITY -2; creatorThread.start ; } } There are two important points to note here. The first is that we deliberately choose to use Vector instead of ArrayLists . Vector s are a slightly slower container structure, but they have one important advantage: all method calls on an instance of Vector are synchronized. This means that some of the problems are solved for us because we dont have to worry about two different add operations being performed simultaneously. The second important point is that object creation is given a lower priority. Object creation is important™weve offloaded it to a background thread™but the last thing we want is for that thread to get priority. Given our druthers, wed rather reuse an object. So, we make creation a slightly lower priority and hope that returned objects satisfy most of the pending requests. We use a separate class, ObjectCreator , to implement the Runnable interface. All Runnable does is wait for requests to come in and then call ThreadedPoo11 s createAndAddObject method: public class ObjectCreator implements Runnable { private ThreadedPool1 _owner; private boolean _requestPending; public ObjectCreatorThreadedPool1 owner { _owner = owner; } public void run { boolean needToCreate = false; while true { synchronizedthis { while _requestPending { try { wait ; } catch InterruptedException ignored{} } needToCreate = _ requestPending; requestPending = false; } if needToCreate { needToCreate = false _owner.createAndAddObject ; } } } public synchronized void askForObject { _requestPending = true; notify ; } }

12.3.6 Adding a Return Thread