Gradually Shrinking the Pool

private Vector _objectsToReturn; private ThreadedPool2 _owner; public ObjectReturnerThreadedPool2 owner { _owner = owner; _objectsToReturn = new Vector ; } public void run { while true { Object objectToReturn; while 0==_objectsToReturn.size { synchronized _objectsToReturn { try { _objectsToReturn.wait ; } catch InterruptedException e{} } } int lastIndex = _objectsToReturn.size -1; objectToReturn = _objectsToReturn.removelastIndex; _owner.returnObjectToPoolobjectToReturn; } } public void validateAndReturnObject object { synchronized_objectsToReturn { _objectsToReturn.addobject; if 1== _objectsToReturn.size { _objectsToReturn.notify ; } } } }

12.3.7 Gradually Shrinking the Pool

The last feature we want to add to our pool is the ability to shrink the pool gradually over time. If you look at pool utilization youll notice an interesting fact: it is usually bursty. That is, most of the time, a small number of pooled objects will suffice. However, during peak usage times, far more objects will be required. This makes the design of ThreadedPool2 rather awkward. In particular, were faced with one of the following choices: • Pass in a large maximum size to the constructor. This means that client threads dont have to wait very long during peak usage. However, this also means that were carrying around a lot of extra baggage during ordinary usage times our pool will almost always have many extra objects in it. • Pass in a smaller maximum size to the constructor and have client threads wait inside the getObject method for a pooled object to become available during peak usage. A compromise solution to this problem is to pass in two values: a maximum size and a steady- state size. They are used to implement the following behavior: • During peak usage, the pool expands to the maximum size and stays that size as long as there is demand for that many pooled objects. • As demand recedes, objects are gradually removed from the pool until the steady-state size is reached. Of course, implementing this behavior requires yet another background thread to remove objects from the pool. This thread is a lot like the lock maintenance and persistence threads we saw earlier in the chapter. In other words, its a background thread that operates on a timer. If enough time has passed without the pool ever being completely emptied, then the background thread removes the surplus object and resumes waiting. This is the final version of our threaded pool: public class ThreadedPool_Final implements Pool { private int _maximumSize; private int _steadyStateSize; private Vector _availableObjects; private int _totalNumberOfObjects; private PoolHelper _helper; private ObjectCreator _creator; private ObjectReturner _returner; private PoolShrinker _shrinker; public ThreadedPool_FinalString poolName, int steadyStateSize, int maximumSize, PoolHelper helper { _steadyStateSize = steadyStateSize; _maximumSize = maximumSize; _helper = helper; _availableObjects = new Vector ; startCreatorThreadpoolName; startReturnerThreadpoolName; startShrinkingThreadpoolName; } public Object getObject { Object returnValue = null; while null== returnValue = getLocallyAvailableObject { _creator.askForObject ; waitForAvailableObject ; } return returnValue; } public void returnObjectObject object { _returner.validateAndReturnobject; } protected void returnObjectToPoolObject 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; } protected void removeAnObject { if _totalNumberOfObjects _steadyStateSize { return; } Object objectToRemove = getLocallyAvailableObject ; if null=objectToRemove { _helper.disposeobjectToRemove; } } private synchronized void waitForAvailableObject { _shrinker.pause ; if we have to wait, the pool is at full utilization try { wait ; } catch InterruptedException e {ignored} _shrinker.resume ; if we had to wait, the pool is at full utilization } 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 ; } private void startReturnerThreadString poolName { _returner = new ObjectReturnerthis; Thread returnerThread = new Thread_returner, poolName + returner thread; returnerThread.setPriorityThread.NORM_PRIORITY+2; returnerThread.start ; } private void startShrinkingThreadString poolName { _shrinker = new PoolShrinkerthis; Thread shrinkerThread = new Thread_shrinker, poolName + shrinking thread; shrinkerThread.setPriorityThread.NORM_PRIORITY -2; shrinkerThread.start ; } } And the code for PoolShrinker is: public class PoolShrinker implements Runnable { private static final int DEFAULT_TIME_TO_WAIT = 120000; private static final int DEFAULT_TIME_INTERVAL = 5000; private ThreadedPool_Final _owner; private int _timeLeftUntilWeShrinkThePool; private boolean _paused; public PoolShrinkerThreadedPool_Final owner { _owner = owner; _timeLeftUntilWeShrinkThePool = DEFAULT_TIME_TO_WAIT; } public synchronized void pause { _paused = true; } public synchronized void resume { _timeLeftUntilWeShrinkThePool = DEFAULT_TIME_TO_WAIT; _paused = false; } public void run { while true { try { Thread.sleepDEFAULT_TIME_INTERVAL; } catch InterruptedException e { ignored} if _paused { decrementClock ; if 0==_timeLeftUntilWeShrinkThePool { _owner.removeAnObject ; resetClock ; } } } } private synchronized void resetClock { _timeLeftUntilWeShrinkThePool = DEFAULT_TIME_TO_WAIT; } private synchronized void decrementClock { _timeLeftUntilWeShrinkThePool-= DEFAULT_TIME_INTERVAL; if 0 _timeLeftUntilWeShrinkThePool { _timeLeftUntilWeShrinkThePool = 0; } } } The last thread we added, which shrinks the pool, is somewhat inefficient. It counts constantly, even if the pool has already been reduced to its steady-state size. It can be easily argued that having a thread do nothing, and do nothing repeatedly, is a bad design. This suggests two possible modifications to our design. The first is that we make the thread wait once steady state is reached. That is, we add a lock object and have the shrinking thread wait on it the thread would get notified the next time an object is actually created. This can easily be implemented within ThreadPool_Final . A second, more interesting design change, is to eliminate the idea of a steady-state size entirely. Right now, the shrinker thread checks two things: whether the pool has had objects in it continually for a long period of time and whether the pool is larger than its steady state. If so, an object is pulled from the pool and destroyed. We could easily change this behavior so that the thread monitors the pool constantly and removes any objects it thinks are surplus, regardless of the pools size.

12.3.8 Two Additional Considerations When Implementing a Pool