RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX


Forking and Joining Java to Maximize Multicore Power

With JSR-166y, Java 7 will get a new way of structuring concurrent programming known as the fork/join framework. Find out how (and when) to use it.

f writing software is hard, then writing applications that will take full advantage of the ever-more-prevalent multicore/multi-CPU hardware—without introducing subtle race conditions and consistency errors—is even harder. Numerous solutions have been floated for Java as well as other environments, solutions such as software transactional memory API libraries, functional languages, and even entirely new environments. However, no solution has emerged as the clear winner or even clear front-runner yet.

For its part, Sun has offered language features for structuring concurrency applications. Java 5 introduced a slew of concurrency data structures as well as behaviors and locking tools as part of the java.util.concurrent package, and Java 6 added a few more thread-safe data structures. Now Sun is set to release the first "new" approach to concurrency since the release of the Thread API in Java 1.0. Based on the work Doug Lea began back in 2000 to port a "work-stealing" mechanism to Java, this new API is known officially as JSR-166y (to distinguish it from the original JSR-166 for Java 5). To the initiated, it is colloquially called the fork/join framework.

When facing a certain type of complex programming problem, a traditional approach has been to split the solution code up into pieces and then spin each piece off into its own thread. However, this divide-and-conquer approach presents developers with one of two problems:

  1. The individual pieces are too large. They simply end up forcing other threads to wait while one or two of them finish their computations.
  2. The pieces are too little. The cost of spinning up the thread itself overwhelms the actual work done, and no benefit is gained.

Getting the granularity just right often leads developers to shout, "Ah, to heck with it!" At which point, they dump all the work in a single thread and don't bother trying to fully optimize it. While this works well during development when the data sets or required execution loads are light, the code eventually bogs down in production as the system tries to scale up. The race condition here is obvious: Will the system need to scale up before the developer can get his resume out to other employers?

Lea found that the divide-and-conquer approach often resulted in premature optimization. The essence of his fork/join approach is to take full advantage of the cores/CPUs available by splitting the work up into a series of independent tasks, forking each one off to a separate processing agent, and then joining the results after all those tasks are done. As presented in Lea's paper, the pseudo-code involved looks something like this:

Result solve(Problem problem) {
  if (problem is small)
    directly solve problem
  else {
    split problem into independent parts
    fork new subtasks to solve each part
    join all subtasks
    compose result from subresults

In other words, one key benefit of the fork/join framework is that it will decide—at the point of solving the problem—whether or not to continue recursively splitting the task into smaller tasks.

Note the use of the term "processing agent" above, instead of "thread." The basic problem mentioned earlier—the cost of spinning up a thread could often outweigh the benefit served by spinning off the thread in the first place—still holds true. For that reason, several tasks may in fact be executed by a single thread in a sequential manner. The best part is this is a tunable parameter, and the developer doesn't need to worry about how and where that sequential decision-making takes place.

Close Icon
Thanks for your registration, follow us on our social networks to keep up-to-date