} _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