|
Use Threading Tricks to Improve Programs
In optimizing, everything counts: programming techniques and the Java platform can help you build a thread-hot application
by Osvaldo Pinali Doederlein
March 2003 Issue
Concurrent programming can be complicated. Despite all the support from the Java platform and specialized tools, implementing complex multithreaded code still must be approached in a rather unscientific fashion. Because threading bugs depend on the random order of asynchronous events, you'll often find them only with sheer (bad) luck, no matter what skills and tools you have. So you'll always need to cross your fingers and test a lot. Still, using smart design and programming practices can help insure that you build a thread-hot application or system service. Let's see what techniques you can use to reduce the number of threading bugs.
There are three steps to developing a multithreaded program. First, make it work. This is the easy part, and that would be the single step for a sequential program. Second, make it thread safe. Now you have to worry about deadlocks, livelocks, and race conditions, and most of us spend a lot of time in this stage. The final stage, of course, is making it run fast. We often spend too much time in the second stage and we're happy if we get through it, but simply making a program multithreaded is not always a guarantee of high performance. Even if you can throw more hardware at the problem, scalability deficiencies will make concurrent programs suboptimal. Worse, most issues in this step depend on fundamental design strategies, not small code optimizations that you can patch afterwards.
In the past there were pthreads and Win32 threads, and only brave programmers used them. Modern environments like Java, though, try to relieve us from daunting tasks such as writing concurrent code. J2EE servers—servlet and Enterprise Java Beans (EJBs) containers—implement a model where we never need to worry about the Thread class or the synchronized keyword. We just write code as usual and the container allocates client requests to pooled threads.
This model works well for certain scenarios. In a transactional application where most data is in the database, all sharing and locking issues are moved to the data access tier, where they're relatively easy to handle. In the worst case, you can rely optimistically on transactional behavior: assume that conflicts are rare and, if they happen, you get a rollback and try again, or reply "Oops, internal error #28847" to the user. For typical serving of Web pages, it's even easier. Applications with a stateless model (or quasistateless, when updates to shared data are rare) are easy to scale up with additional threads. Somebody somewhere must still implement sophisticated concurrent code; for instance, your EJB server may use a shared database cache for container-managed persistence (CMP) beans, but in this scenario we rely on the platform.
The Java Runtime Environment (JRE) is a part of the solution. Initially, performance issues focused on fundamental optimizations in the compiler and system libraries. Multithreading and memory management became bigger concerns by the time Java was recast as a server platform (Java 2), and in the last few years this platform is where we see most improvements.
Back to top
|