• No results found

The Pool library provides both a suite of interfaces and a set of concrete imple- mentations. This allows a developer to maintain compatibility with a wide variety of applications, while also adding new functionality as needed. In turn, the Pool library contains two hierarchies—one for a generic pool, and one for keyed pools. The hierarchy for generic pools is shown in Figure 5-1.

The semantics for an object pool are fairly straightforward—you must first

implement the interface org.apache.commons.pool.PoolableObjectFactory(a no-

operation abstract class, org.apache.commons.pool.BasePoolableObjectFactory,

can be subclassed if preferred). At a minimum, you will need to provide an

implementation of themakeObject()method, to define how and what should be

created.

Next, you will need to create an ObjectPool. You can create an ObjectPool

by using an ObjectPoolFactory or simply by allocating it using new, passing

your PoolableObjectFactory implementation in to the constructor. Depending

on the ObjectPoolimplementation you use, your object pool will have different

behavior. The GenericObjectPool uses a first in, first out behavior, suitable for

situations in which you want to ensure that all of the objects are used fre-

quently. The StackObjectPool, on the other hand, uses a last in, first out behav-

ior. A useful alternative to the StackObjectPoolis the SoftReferenceObjectPool,

which allows garbage collection of unused objects.

The KeyedObjectPoolhierarchy, as shown in Figure 5-2, is similar to the GenericPool, but with the addition of a key value. This allows you to maintain a single pool with the elements retrievable by key. The default implementa- tions allow one or more objects per key, but you could provide an implementa- tion that restricts use to a single object per key.

THREAD

POOL

EXAMPLE

Java features a powerful, rich set of services for working with multiple threads. In this example, we will use the Pool suite to manage experimenta- tion with the built-in Java threading facilities.

As shown in Figure 5-3, a WorkerThreadFactorycan be used to provide new

test thread objects. Our main application code,PoolTest, creates a suite of test

threads, testing how long it takes to complete the test run in different combi- nations of threads and thread runs.

By changing the behavior of the WorkerThread.run() method, we can

change the load placed on the system by each thread, and by changing the val-

ues of the PoolTest.runTest() method, we can determine different results

when different thread loads are placed on the system. For example, by com- paring the impact of 1,000 threads running once against a single thread run- ning the same task 1,000 times, we start to understand the overhead of parallel execution on a single system compared to serial execution.

Figure 5-2 K eyed pool c lass hierarc hy . 70

Thread Pool Example 71

Listing 5-1 shows the main class for this example. Pay particular atten-

tion to the configuration of the GenericObjectPool object in the runTest()

method—notice that several important configuration methods are used to set

the operation of the pool. You’ll also notice that in this example,runTest()bor-

rows objects from the pool but doesn’t return them—the WorkerThread objects

return themselves to the pool when execution is complete.

Listing 5-1 Thread Test Runner package com.cascadetg.ch05;

import org.apache.commons.pool.impl.GenericObjectPool; public class PoolTest extends Thread

{

/** This main method simply creates a new PoolTest thread * and starts it. */

public static void main(String[] args) {

new PoolTest().start(); }

/** The run() method (called when the PoolTest thread starts) * merely executes a series of test in serial fashion. */

public void run() { runTest(30, 30); runTest(100, 100); runTest(1000, 1000); runTest(1000, 2000); runTest(1, 3000); runTest(10, 3000); runTest(30, 3000); runTest(1000, 3000);

System.out.println("Most efficient @" + mostEfficientTime); System.out.println(

"(" + mostEfficientThreads + "/" + mostEfficientRuns + ")"); System.out.println("Done."); }

/** Used to keep track of the most efficient run */ int mostEfficientThreads = 0;

int mostEfficientRuns = 0; float mostEfficientTime = 0;

/** This method actually runs the test. The higher the * maxThreads value, the more threads can be created to run in * parallel. The threadRuns indicates the total number of * threads that will be created.

*

* @param maxThreads Used to specify the maximum number

72 Pool Chapter 5

Listing 5-1 (continued)

* of threads to be created by the object pool. *

* @param threadRuns The number of executions of the thread * to be run.

*/

public void runTest(int maxThreads, int threadRuns) {

System.out.println(

"Starting " + maxThreads + "/" + threadRuns);

// Create an instance of our WorkerThreadFactory.

WorkerThreadFactory myFactory = new WorkerThreadFactory(); // Here, we create a generic object pool, passing in our // WorkerThreadFactory.

GenericObjectPool myPool =

new GenericObjectPool(myFactory);

// Here, we configure the behavior of our pool.

// Note the use of maxThreads to configure the number of // threads we want to allocate, and the behavior of the // threads.

myPool.setMaxActive(maxThreads); myPool.setWhenExhaustedAction(

GenericObjectPool.WHEN_EXHAUSTED_BLOCK); myPool.setTestOnReturn(true);

// Gather the current timing info, and start making // threads. Note that this will block if there is no // thread available.

long currentTime = System.currentTimeMillis(); for (int i = 0; i < threadRuns; i++)

{ try { WorkerThread myThread = (WorkerThread)myPool.borrowObject(); myThread.setPool(myPool); myThread.start(); } catch (Exception e) { e.printStackTrace(); } }

// Now, make sure that all of the threads we kicked off // have a chance to finish up what they are doing. while (myPool.getNumActive() > 0) { yield(); } // Let's do some reporting of the results.

long time = System.currentTimeMillis() - currentTime; System.out.println(

"Total created threads:" + myFactory.currentThread);

System.out.println("Seconds Elapsed: " + (time / 1000f)); System.out.println(

"Completed: " + WorkerThread.totalUnits); float efficiency = WorkerThread.totalUnits / (time / 1000f);

Thread Pool Example 73

Listing 5-1 (continued)

System.out.println("units/second: " + efficiency); // If this is our most efficient run, we should note that. if (efficiency > mostEfficientTime) { mostEfficientTime = efficiency; mostEfficientThreads = maxThreads; mostEfficientRuns = threadRuns; } System.out.println();

// Reset the work done by the threads for the next test. WorkerThread.totalUnits = 0;

} }

OBJECT

FACTORY

EXAMPLE

The object factory, shown in Listing 5-2, implements the PoolableObjectFactory

interface. The two key methods implemented are makeObject() and

validateObject(). The factory always returns false for an invalid object because when a thread enters a finished state, it cannot be reused. Because the pool is configured to always test on return (as shown in Listing 5-1), this

means that the WorkerThreadwill be removed when completed.

Listing 5-2 Thread Factory package com.cascadetg.ch05;

import org.apache.commons.pool.PoolableObjectFactory;

public class WorkerThreadFactory implements PoolableObjectFactory {

/** Keeps track of the currently created thread. */ public int currentThread = 0;

/** Create and name the thread. Naming the thread is very * helpful when trying to debug multi-threaded applications. */

public Object makeObject() throws Exception {

WorkerThread temp = new WorkerThread();

temp.setName("Worker Thread #" + currentThread++); return temp;

}

/** We aren't reusing threads, so we always return false here, * causing the pool to remove this thread from the pool and * create a new object using the makeObject() method.

*/

public boolean validateObject(Object arg0) { return false; } public void destroyObject(Object arg0) throws Exception { }

74 Pool Chapter 5

Listing 5-2 (continued)

public void activateObject(Object arg0) throws Exception { } public void passivateObject(Object arg0) throws Exception { } }

WORKERTHREAD

Listing 5-3 shows our worker thread. The default implementation merely counts to 1,000, yielding every count. This is overly aggressive, but it does allow the system to remain highly responsive to other threads during execu- tion. It’s easy to imagine changing the behavior of this worker thread to do something more computationally complex—for example, when used in con- junction with the networking capabilities shown in Chapter 4, "Net" and this chapter, parallel execution could well be much faster and easier.

The implementation shown in Listing 5-3 drops out of the run()method

when complete, rendering the thread no longer reusable. It would be possible

to create a reusable WorkerThread—instead of completing and dropping out of

the run()method, the thread could instead have two states—a busy state and an idle state, with the thread returning itself to the pool when switching from the busy state to idle.

Listing 5-3 Worker Thread package com.cascadetg.ch05;

import org.apache.commons.pool.ObjectPool; public class WorkerThread extends Thread {

// The total amount of work done by the threads. static public long totalUnits = 0;

// The number of times the thread should look over a counter // (this is our definition of work)

private int counter = 1000;

// When the thread is done, it returns itself to the pool. private ObjectPool hostPool = null;

// Used to indicate that a thread, when completed, is no longer // useful.

public boolean valid = true;

public void setPool(ObjectPool myPool) {

hostPool = myPool; }

public void run() {

// Loop over a counter, and yield each time to allow other // threads to execute. In a "real" app, you wouldn't need // to yield anywhere near this often.

for (int i = 0; i < counter; i++)

Worker Thread 75

Listing 5-3 (continued) { totalUnits++; yield(); } try {

// We synchronize on the pool to avoid possible // threading problems, and return our object. synchronized (hostPool) { this.valid = false; hostPool.returnObject(this); } } catch (Exception e) { e.printStackTrace(); } } }

Finally, Listing 5-4 shows an example of the output of this application. Pay- ing close attention to the output, we can see that in this particular case, a non-parallel approach has the best timing. This is to be expected in a CPU- dependent, single system environment, but in environments involving poten- tially slow resources (such as network access), the results may be quite different. Listing 5-4 Sample Output

Starting 30/30

Total created threads:30 Seconds Elapsed: 0.191 Completed: 30000 units/second: 157068.06 Starting 100/100

Total created threads:100 Seconds Elapsed: 0.801 Completed: 100000 units/second: 124843.945 Starting 1000/1000

Total created threads:1000 Seconds Elapsed: 9.794

Completed: 1000000 units/second: 102103.33 Starting 1000/2000

Total created threads:2000 Seconds Elapsed: 19.127 Completed: 2000000 units/second: 104564.23 Starting 1/3000

Total created threads:3000 Seconds Elapsed: 5.398

Completed: 3000000 units/second: 555761.44

76 Pool Chapter 5

Listing 5-4 (continued) Starting 10/3000

Total created threads:3000 Seconds Elapsed: 6.85

Completed: 3000000 units/second: 437956.22 Starting 30/3000

Total created threads:3000 Seconds Elapsed: 7.641

Completed: 3000000 units/second: 392618.78 Starting 1000/3000

Total created threads:3000 Seconds Elapsed: 28.471 Completed: 3000000 units/second: 105370.375 Most efficient @ 555761.44 (1/3000) Done.

SUMMARY

This chapter shows how to use an implementation of a common pattern—the use of a pool. Pools typically serve one of two roles—either as a logical repre- sentation of a pool, or as a performance enhancement by way of a cache imple- mentation. If a pool represents a logical pool, you should feel free to use the pattern immediately—for example, an application for reserving hotel rooms might use a pool to track the available rooms. If a pool represents a cache of some sort, it should be added to an application later rather than sooner to avoid premature optimization.

Pools are commonly used as a performance enhancement for manag- ing connections to a database. The next chapter will look at a specific pool designed for managing database-specific resources.

Project Ideas

Build an application that uses the Pool package to wrap Net NNTP con- nections (as described in Chapter 4). Add a user interface to allow a user to browse the NNTP articles on one thread and then a configurable num- ber of other threads to download articles in the background.

Build a test harness that tests the size and options available for differ- ent pools against the factories. Given this information and other data available to a running Java application (such as the current memory available), would it be possible to build a self-tuning pool?

C H A P T E R

6