The Idea of a Pool Two Interfaces That Define a Pool

12.2.13 Use Worker Threads to Prevent Deadlocks

Another common trick to prevent deadlocks is to use worker threads to reduce the number of locks any given thread has. Weve already briefly discussed worker threads. Among other examples, we discussed log files. Our example began with: A single thread that both received and handled a request and, in the course of doing so, logged information to the log file. We transformed this into: Two threads. One thread received the request and encapsulated the request in an object that was dropped off in a container. The second worker thread, pulled these objects from the container and registered them in a log. The main reason I gave for doing this was to prevent clients from blocking each other, or waiting for an external device to become available. Logging is something that can often be done independently of the actual client request. But theres another benefit. In the first scenario, a single thread holds two distinct types of locks: locks associated with the actual client request and incidental locks associated with the logging mechanism. Using a logging thread changes this in a very significant way: There is only one lock associated with logging that the request thread ever acquires namely, the lock associated with the container used to communicate with the worker thread. Moreover, the lock associated with the container doesnt count when youre reasoning about deadlock. This is because the container lock has the following two properties: • If another lock is acquired before the container lock, that other lock will be released after the container lock is released. • No locks are acquired between when the container lock is acquired and when the container lock is released. Any lock with these two properties cannot add a deadlock to a system. It can block threads as the container lock does in order to ensure data integrity, but it cannot deadlock threads.

12.3 Pools: An Extended Example

At this point, youve read some 70 or so pages of reasonably plausible material on threading. But, speaking from personal experience, its almost impossible to read 70 pages of material on threading and actually understand all of it on the first reading. The point of this section is to introduce a fairly complex and sophisticated piece of code that involves threading. If you understand this example, how it works, and why its built the way it is, then youve got a reasonable grasp of this material and how to use threads in your applications. If, however, this seems like an incredibly byzantine and opaque piece of code, then you may want to reread those 70 pages or grab a copy of one of the references.

12.3.1 The Idea of a Pool

Pooling is an important idiom in designing scalable applications. The central idea is that there is a resource, encapsulated by an object, with the following characteristics: • It is difficult to create the resource, or doing so consumes other scarce resources. • The resource can be reused many times. • You frequently need more than one instance of the resource because there are many threads that perform tasks involving this type of resource I will call these threads the client threads. The canonical example of a resource that ought to be pooled is a database connection. In Java, database connections are embodied as instances of an implementation of the java.sql.Connection interface. A database vendor will supply a class that implements the Connection interface and is used to communicate with the vendors database server. Database connections almost always involve a socket connection to the database server. Socket connections to the database server are expensive for two reasons. First, the database has a limited number of sockets it can vend. Second, establishing a connection often involves logging into the database and establishing a secure communications channel. Performing the security check is time-consuming and is something you dont want to repeat every time you need to make a query against a database.

12.3.2 Two Interfaces That Define a Pool

Our goal is to define a generic and reusable pooling mechanism. To do so, we start by defining two interfaces: Pool and PoolHelper . These are very simple interfaces, defined in the com.ora.rmibook.chapter12.pool package: public interface Pool{ public Object getObject ; public void returnObjectObject object; } public interface PoolHelper { public Object create ; public boolean disposeObject object; public boolean isObjectStillValidObject object; } Pool and PoolHelper encaspulate everything that the client threads which simply use the pooling mechanism need to know. Pool defines only two methods: getObject lets a client thread get an object from the pool, and returnObject returns an object to the pool thereby making it available for other client threads to use. The second interface helps us build a generic and reusable pool class. Since theres no way an implementation of the Pool interface could know how to construct the objects in the pool, or how to make sure theyre still valid, we need to provide a way for the generic pooling mechanism to create and validate individual objects in the pool. For example, database connections have a tendency to fail over time. The pooling mechanism should occasionally check them and make sure they still work. We build this into our system by defining the PoolHelper interface. Users of the pooling mechanism will implement PoolHelper s three methods in order to customize the pool to their specific needs.

12.3.3 A First Implementation of Pooling