Ivan Kiselev

Subscribe to Ivan Kiselev: eMailAlertsEmail Alerts
Get Ivan Kiselev: homepageHomepage mobileMobile rssRSS facebookFacebook twitterTwitter linkedinLinkedIn


Related Topics: Java Developer Magazine

Java Developer : Article

Resource Pooling In Java

Resource Pooling In Java

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
Java's threading model lends itself nicely to shared access to resources. In this article I assume that resources are shared among concurrent threads, not among the operating system's processes. The implementation described below can be easily adapted for a multiprocess situation by developing an interprocess communication layer on top of it.

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.
Listing 3 presents an implementation based on a generic Pool class that was discussed in the previous section. Note: A lot of details were omitted for the sake of clarity, namely, initialization, synchronization, and proper exception handling.

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).
Exercise 2
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.
Unfortunately, as with any compromise, these implementation details force the user to exercise some caution regarding what can be safely stored in this pool. The following convention must be observed: an object name must uniquely identify an interface for the purposes of the CORBAConnectionsPool class. For example, if there are two interfaces, Laser and Bubble, that extend the Device interface, and both CORBA services that implement Laser and Bubble are named the same (say, "Printer," or not named at all), the code in Listing 5 would lead to storing an ambiguous reference in the CORBAConnectionsPool that was caused by the polymorphism. The same will happen if we try to manage multiple objects with the same name and completely unrelated types, but for a different reason: the CORBAConnectionsPool class doesn't keep track of object IDs along with object names, which leads directly to Exercise 3.

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.
While exploring future possibilities of resource pooling, it's important to concentrate on the usability of the perspective solution: interfaces that are too general or complex may encourage developers to choose a resource-specific solution.

More Stories By 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.

Comments (0)

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.