| By Ivan Kiselev | Article Rating: |
|
| November 1, 2000 12:00 AM EST | Reads: |
13,318 |
All major and minor application server vendors heavily advertise the connection pooling functionality of their respective offerings. In this article I examine what's involved in developing resource pooling features from the perspective of a Java developer. I feel the subject is both greatly overhyped and underrepresented in technical literature.
The Problem
First, I'll try to describe the problem in very general terms. I'll rely more on practical considerations rather than scientific completeness:
- A set of resources has one common characteristic: there's a limited supply. Usually the supply limitation isn't related to the scarcity of the resource, but to the difficulty of obtaining it. For example, it's possible to open a database connection for each query, but it's a time-consuming operation.
- It's assumed that these resources will be needed by different parts of an application that will "compete" for them according to some application logic.
- Each individual resource can be used an unlimited number of times.
Translating plain English into Java, it looks like a collection of resources that:
- Contain a pool of resources to be managed
- Supply the needed resource on demand
- Deny the resource if none is available
- Exercise some policy on resource availability including queuing, waiting, and so on
Generic Implementation
The generic implementation is provided in the form of the class Pool (see Listing 1).
Theoretically speaking, the Pool class can be a Stack; for example, Pool can extend Stack with the Pool.pop(long time) method, override Stack.pop() and used (semantically) in place of tryPop(). Nevertheless, I've chosen delegation over inheritance and the reasons are:
- The Stack.pop(), and Pool.pop() methods have different semantics. The former doesn't really "try" to pop it; it's quite unconditional in its attempt to pop the object and will throw an exception if unable to do so.
- The other methods of the generic Stack class will violate the integrity of the pool. For example, Stack.peek() will deliver a potentially shared object to anybody who asks; Vector.clear(), inherited from the superclass, will destroy a stack-based Pool altogether. You can also "mute" all of Stack's methods that Pool doesn't need, but it will confuse the class's users even further.
Let's consider how the Pool class can be used. First, an application must create a Pool object, usually a static one. Second, the Pool must be populated with the resources to be managed. After that the Pool is ready to use (see Listing 2).
In Listing 2 the method usageExample() uses a static instance of the Pool class (named staticPool) created and initialized elsewhere in the class. The type "Resource" represents any particular type of resource that the application will need. The resource is obtained from the pool via the pop() method that accepts a waiting time parameter (in seconds) (e.g., the Pool class will try for 10 seconds to obtain the resource, if it's not currently available). During that time the pool will check 10 times if some other thread put any of the resources back and will return it if so. If none of the resources become available, the pool will return null and it's up to the application to decide how to proceed further; the example in Listing 2 just gives up and throws an exception.
An important aspect of using pools is that a resource obtained from a pool must be returned after the application is done using it. This is guaranteed by the "finally" clause in the example: if the resource was successfully obtained (not null), then it will be returned (call to the Pool.push() method). Obviously, if anything prevents the return of the resource, it creates a resource leak that will eventually deplete the pool.
The synchronization of the Pool's methods deserves special attention. Since java.util.Stack class is used in implementing the Pool, there's a certain sense of security as far as multithreading is concerned. The Stack class is, in turn, implemented as a java.util.Vector, which is properly synchronized. Unfortunately, this sense of security proves to be a false one and for the following reason: method Pool.tryPop() prevents the possibility of popping the empty stack (with related complications in the form of a nasty runtime EmptyStackException) by checking it via Stack.empty(). The problem arises if the execution path veers off the current thread after the Stack.empty() call and some other thread gets the last resource in the pool, then the current thread does pop an empty stack! It's hard to estimate how often it will happen, but it'll be too late to think about it when exceptions start flying out of your complete and attractively shrink-wrapped product. So we have to synchronize the Pool.tryPop() method; however, the rest of Pool's methods don't need any additional synchronization. Pool.push() is synchronized via its superclass Vector and Pool.pop(long time) does all its magic via Pool.tryPop().
Exercise 1
The pop() method uses a very simple waiting algorithm - it tries to get the resource 10 times per waiting period. The overall performance of the system that uses resource pools can be significantly improved if a more adaptive waiting algorithm is utilized. One such algorithm I'm aware of can be described as follows: start with a very short waiting time (say, 1/100 of the total wait) and double it with each iteration until either the waiting limit is exhausted or a resource is obtained. The effect is that if no resource is available immediately, the pool will try more frequently in the beginning of the wait and less so toward the end; it can be called a "vanishing hope" method. For a system that operates near its performance capacity, it results in better response time during occasional overloads. The implementation of improved waiting schemes is left as an exercise to the reader.
Database Connections
The resource pooling is most frequently used for database connections. Naturally, database connections are presumed to be JDBC connections for the purposes of this discussion. Several specific database issues warrant special consideration with regard to resource pooling:
- A database connection can and will be broken, in my experience, during the application instance's life span. Some drivers (could be all of them) may not restore connections if their respective database servers are restarted.
- It's impractical to create all possible database connections during an application's start-up. It takes forever and all these connections may not be needed.
These issues lead to the following requirements for database connection pooling:
- Connections should be periodically checked for validity.
- Connections should be dynamically allocated as needed.
The DbConnectionsPool class extends the generic Pool with the following functionality:
- It can create new database connections. All parameters that are needed to do so (driver, user name and password, maximum number of connections allowed, etc.) are passed to the class constructor and later used by the createConnection() method.
- Its constructor starts a thread that periodically checks connections for validity and destroys bad ones.
- The pop() method creates new connections (up to the specified maximum number) when the pool is depleted.
- isGood() method tries to send a "null-operation" to the database just to make sure it can go to the server and back. Since all the connections to be tested aren't used by any pending transactions, the statement "rollback work" should do nothing (check with your particular database's documentation).
The run() method removes a connection only when it goes bad. It might be very useful to create an algorithm that will destroy connections according to their usage patterns. For example, the winning strategy might be to create new connections when needed and remove old ones when demand for them is low. It'll reduce memory requirements of the application and may ease up the load on the database server. An implementation of such an algorithm is offered as an exercise. Hint: Compare currentConnections counter with the number of connections in the pool while checking for validity. If these two numbers are close, then connections are underutilized.
CORBA Connections
The CORBA world offers its own set of complications as far as resource pooling is concerned. The good news is that CORBA connections are automatically rebound (that's CORBA-speak for "reconnected") in case of failure (at least, the implementation I use - Inprise's VisiBroker - does this). The bad news is that the CORBA connection pool implementation has to deal with the fact that CORBA connections, unlike JDBC ones, are not created equal. A typical application usually connects to several different CORBA objects that are distinguished by their type and name.
It's quite possible to create separate connection pools for each object type and name combination, but it leads to code bloat and, generally, reduces the reliability and maintainability of an application. Let's see how the situation can be remedied.
Consider the CORBAConnectionsPool presented in Listing 4 (again, many implementation details are omitted). The basic idea is simple: create a separate pool for each object name and automatically open new connections using the same strategy that was used with the DbConnectionsPool class. More details on this approach:
- We distinguish CORBA objects by specifying their respective names and IDs as parameters in pop(...) methods.
- The object ID can be omitted if an object inherits from a GenericObject CORBA interface. The CORBAConnectionsPool class is aware of this interface and "knows" how to get its object ID, creating a valid ID to bind to any of its subclasses. The right reference can be obtained by the actual application by narrowing down (CORBA's equivalent of type casting) the reference to the GenericObject retrieved from the pool.
Exercise 3
Since the above-described implementation distinguishes CORBA objects only by their names, develop a CORBA connection pool that will also account for object IDs.
The overall usage pattern for CORBAConnectionsPool follows the example presented for a generic Pool in Listing 2 with changes dictated by CORBAConnectionsPool method signatures. Again, as always, it's extremely important to guarantee the return of the used connection back to the pool.
The most compelling case for using CORBAConnectionsPool despite its object ID indifference is when an application has to deal with a large number of objects of the same interface, for example, printer services on a network. It's also quite convenient for the CORBA services that are part of a larger system to implement some common generic interfaces, such as GenericObject, in the example above. This interface can be used to implement common interface features that are related to some application group (e.g., for life-cycle management).
Conclusion
As you can see, it's a relatively straightforward task to develop a resource pooling solution in Java, thanks to built-in multithreading. Unfortunately, a developer has to take care of a lot of implementation details (careful synchronization is one example) to produce a resource pool for production-level heavy lifting. Even more unfortunate, domain-specific pools require drastic changes to the interface compared to generic ones. Java development of resource pooling can be taken a bit further by exploring the possibilities of developing a general framework that includes these features:
- A flexible mechanism for defining resource availability policies that vary depending on the application: For example, a database has different performance characteristics than a Web server and, therefore, these two require different resource strategies including waiting and queuing.
- Ability to accommodate resources of various types without any resource-specific knowledge on the framework's part: As shown above, a database connection requires different caretaking compared to a CORBA link. It should be possible to provide generic interfaces for resource creation, initialization, and upkeep regardless of the particular type.
- "Learning" runtime behavior with regard to resource creation and clean up: For example, it should be possible for some applications to predict their demands for a particular resource type and prepare the resource pool accordingly. For example, authentication services are loaded mostly in the morning, printers are at night, and the fax load depends on the long-distance fee schedule.
Published November 1, 2000 Reads 13,318
Copyright © 2000 SYS-CON Media, Inc. — All Rights Reserved.
Syndicated stories and blog feeds, all rights reserved by the author.
About Ivan Kiselev
Ivan Kiselev is chief architect at APP Design Group, Inc. His professional interests include applications of reusable frameworks and application servers to electronic commerce systems, development environments, and integration of scripting languages into all of the above.
- Performance of Java Compilers: An Empirical Study
- Java Kicks Ruby on Rails in the Butt
- Ulitzer’s Amazing First 30 Days in Public Beta
- 1st Annual Government IT Expo: Call for Papers Deadline July 15
- REA Is Where RIA Becomes the Norm
- Why an Application Grid?
- Will Ulitzer Dominate News Content on The Web? -Gartner
- Clear Toolkit 4: The Road Map
- Profiling Netbeans within Amazon EC2
- Java Persistence on the Grid: Approaches to Integration
- Performance of Java Compilers: An Empirical Study
- Java Kicks Ruby on Rails in the Butt
- Developing Rich Client Applications Using Swing - II
- The Right Time for Real Time Java
- Xpress Suite Adds Automatic Java to iPhone Conversion
- Initial Thoughts on IBM Acquisition of Sun Microsystems
- Ulitzer’s Amazing First 30 Days in Public Beta
- 1st Annual Government IT Expo: Call for Papers Deadline July 15
- Maximizing Java Performance with Bespoke Programming
- Pet Store with JavaFX 1.0.- Part I
- A Cup of AJAX? Nay, Just Regular Java Please
- Java Developer's Journal Exclusive: 2006 "JDJ Editors' Choice" Awards
- The i-Technology Right Stuff
- JavaServer Faces (JSF) vs Struts
- Rich Internet Applications with Adobe Flex 2 and Java
- Java vs C++ "Shootout" Revisited
- Bean-Managed Persistence Using a Proxy List
- Reporting Made Easy with JasperReports and Hibernate
- What's New in Eclipse?
- Creating a Pet Store Application with JavaServer Faces, Spring, and Hibernate






































