5/15/13

Sponsored by:

http://www.javaworld.com/javaworld/jw-06-2012/120626-modern-threading.html

This story appeared on JavaWorld at http://www.javaworld.com/javaworld/jw­06­2012/120626­modern­threading.html

Modern threading: A Java concurrency primer
Understanding Java threads in a post­JDK 1.4 world
By Cameron Laird, JavaWorld.com, 06/26/12 Much of what there is to learn about programming with Java threads hasn't changed dramatically over the evolution of the Java platform, but it has changed incrementally. In this Java threads primer, Cameron Laird hits some of the high (and low) points of threads as a concurrent programming technique. Get an overview of what's perennially challenging about multithreaded programming and find out how the Java platform has evolved to meet some of the challenges. Concurrency is among the greatest worries for newcomers to Java programming but there's no reason to let it daunt you. Not only is excellent documentation available (we'll explore several sources in this article) but Java threads have become easier to work with as the Java platform has evolved. In order to learn how to do multithreaded programming in Java 6 and 7, you really just need some building blocks. We'll start with these: A simple threaded program Threading is all about speed, right? Challenges of Java concurrency When to use Runnable When good threads go bad What's new in Java 6 and 7 What's next for Java threads This article is a beginner's survey of Java threading techniques, including links to some of JavaWorld's most frequently read introductory articles about multithreaded programming. Start your engines and follow the links above if you're ready to start learning about Java threading today.  

A simple threaded program
Consider the following Java source.

www.javaworld.com/cgi-bin/mailto/x_java.cgi?pagetosend=/export/home/httpd/javaworld/javaworld/jw-06-2012/120626-modern-t…

1/10

 You'll see output that looks something like this: Listing 2. c o u n t .m e a n s . Output of a threaded program L i n e# 1f r o mt h r e a d' A ' L i n e# 1f r o mt h r e a d' C ' L i n e# 1f r o mt h r e a d' B ' L i n e# 2f r o mt h r e a d' C ' L i n e# 3f r o mt h r e a d' C ' L i n e# 2f r o mt h r e a d' B ' L i n e# 4f r o mt h r e a d' C ' .r o w=1 . E x a m p l e T h r e a dm t=n e wE x a m p l e T h r e a d ( " A " .g e t N a m e ( ) ) . s l e e p ( d e l a y ) . L i n e# 1 7f r o mt h r e a d' B ' L i n e# 1 4f r o mt h r e a d' A ' L i n e# 1 8f r o mt h r e a d' B ' L i n e# 1 5f r o mt h r e a d' A ' L i n e# 1 9f r o mt h r e a d' B ' L i n e# 1 6f r o mt h r e a d' A ' L i n e# 1 7f r o mt h r e a d' A ' www.html Listing 1.javaworld.f o r / / i n s t a n c e . o u t . s t a r t ( ) .com/javaworld/jw-06-2012/120626-modern-threading. s u p e r ( " t h r e a d' "+l a b e l+" ' " ) . } } c l a s sE x a m p l e T h r e a de x t e n d sT h r e a d{ p r i v a t ei n td e l a y . ." p r i n tal i n ee v e r y / / h u n d r e d t ho fas e c o n d " . " 1 0 " . d e l a y=d . T h ed e l a yi s / / m e a s u r e di nm i l l i s e c o n d s . . p u b l i cE x a m p l e T h r e a d ( S t r i n gl a b e l .r o w<2 0 .cgi?pagetosend=/export/home/httpd/javaworld/javaworld/jw-06-2012/120626-modern-t… 2/10 . f o r m a t ( " L i n e# % df r o m% s \ n " . } c a t c h( I n t e r r u p t e d E x c e p t i o ni e ){ / /T h i sw o u l db eas u r p r i s e . FirstThreadingExample c l a s sF i r s t T h r e a d i n g E x a m p l e{ p u b l i cs t a t i cv o i dm a i n( S t r i n g[ ]a r g s ){ / /T h es e c o n da r g u m e n ti sad e l a yb e t w e e n / / s u c c e s s i v eo u t p u t s . m t 2 .3 1 ) .5/15/13 http://www.r o w + + .c o u n t + + ){ t r y{ S y s t e m . m t .i n td ){ / /G i v et h i sp a r t i c u l a rt h r e a da / / n a m e : " t h r e a d' L A B E L ' " .1 0 ) . E x a m p l e T h r e a dm t 2=n e wE x a m p l e T h r e a d ( " B " . m t 3 . } } } } Now compile and run this source as you would any other Java command­line application.2 5 ) . E x a m p l e T h r e a dm t 3=n e wE x a m p l e T h r e a d ( " C " . T h r e a d . s t a r t ( ) .com/cgi-bin/mailto/x_java. s t a r t ( ) . } p u b l i cv o i dr u n( ){ f o r( i n tc o u n t=1 .javaworld. c u r r e n t T h r e a d ( ) .

javaworld. maybe not so fast. that I previously said the output for F i r s t T h r e a d i n g E x a m p l e  would look "something like" Listing 2. . with appropriate tracing in and out of other methods. there is a guarantee of order­of­execution: the first line in m a i n ( )  will be executed first. But that power comes at the cost of determinacy. So indeterminacy isn't just a weakness in the system; it's also an enrichment of the model of execution.d e l a y ) . The basic idea is that the program starts three separate T h r e a d s. there is a guarantee of order­of­execution: the first line in m a i n ( )  will be executed first. Indeterminacy may be unfamiliar. then the next. and there are also advantages associated with indeterminacy. Event listeners in Swing or event handlers in HTML are examples. though. line by line. with appropriate tracing in and out of other methods. which then run independently until completion. that means your output could be different from mine.html L i n e# 1 8f r o mt h r e a d' A ' L i n e# 1 9f r o mt h r e a d' A ' That's it ­­ you're a Java T h r e a d  programmer! Well. one that gives the programmer new opportunities to determine sequence and dependency. T h r e a d  weakens that guarantee. Threads and indeterminacy A typical learning cycle with programming consists of four stages: (1) Study new concept; (2) execute sample program; (3) compare output to expectation; and (4) iterate until the two match. In a multithreaded program. To make their execution more instructive. Order­of­execution within a thread remains predictable. even on the same computer." and the order might differ on successive executions of the same program. The www. require handling an I n t e r r u p t e d E x c e p t i o n . Note. in general. but it needn't be disturbing. While a full discussion of thread synchronization is outside the scope of this introduction.  ­­ and tinkering with the d e l a y s. . then the next. So.com/javaworld/jw-06-2012/120626-modern-threading.5/15/13 http://www. okay. m y F u n c t i o n ( )  isn't executed at a definite time with respect to other elements of source code.o n c l i c k=" m y F u n c t i o n ( ) . What's that about? In the simplest Java programs. Threading brings new power to Java programming; you can achieve results with threads that you couldn't do without them. each one delays slightly between the successive lines it writes to output; this gives the other threads a chance to write their output. consider the mechanics of how HTML specifies . . and so on. In the simplest Java programs.n e wE x a m p l e T h r e a d ( l a b e l .com/cgi-bin/mailto/x_java. Note that T h r e a d ­based programming does not. and so on.  to determine the action that will happen after the user clicks. As small as the program in Listing 1 is. it contains some subtleties that merit our attention. . "L i n e# 1 7 f r o mt h r e a dB " might appear on your screen before or after "L i n e# 1 4f r o mt h r e a dA . Try adding or removing E x a m p l e T h r e a d s ­­ that is. T h r e a d  weakens that guarantee. constructor invocations like . but in relation to the end­user's action.javaworld. You might have experienced something similar when working with graphical user interfaces (GUIs). In this case.cgi?pagetosend=/export/home/httpd/javaworld/javaworld/jw-06-2012/120626-modern-t… 3/10 . it's easy to explain the basics. For instance. This familiar case of indeterminacy illustrates some of its advantages. Execution delays and Thread subclassing You can learn from F i r s t T h r e a d i n g E x a m p l e  by experimenting with it on your own. ". . .

Think of a GUI application: if it still responds to end­user points and clicks while searching "in the background" for a matching fingerprint or re­calculating the calendar for next year's tennis tournament.5/15/13 http://www. multithreaded programs are likely to give different results in terms of the exact sequence or timing of thread execution.  T h r e a d  naturally expresses the application's required programming model. that you were modeling the behavior of rush­hour automobile drivers or bees in a hive. When multiple processing cores are available to the JVM. multimedia playback. apart from any considerations of speed or responsiveness. when confronted with a problem.html one shown in F i r s t T h r e a d i n g E x a m p l e  has to do with s l e e p ( ) . complicated calculations. But the main point of enduring all these difficulties isn't to gain speed. (See "Swing threading and the event­dispatch thread" for further illustration of these principles. That's funny because it so well models the problem with concurrency. in general. right? So by now you can see a little bit of what makes programming with threads complex. the behavior of long­running methods found "in the wild. Its default r u n ( )  method does nothing. or when the program spends significant time waiting on multiple external resources such as network responses. The fundamental added value of multithreaded programs is responsiveness. I'll use threads. Multithreaded programs do not. then. complete faster than single­threaded ones ­­ in fact they can be significantly slower in pathologic cases. in a simple way. designed to be subclassed. 2.javaworld. A typical concurrent application architecture puts recognition and response to user actions in a thread separate from the computational thread assigned to handle the big back­end load. That's www. you're most likely to consider using T h r e a d s in one of these circumstances: 1. so must be overridden in the subclass definition to accomplish anything useful. Suppose. This might be the case for someone rendering complex graphics or simulating an involved scientific model. These "blocks" often have to do with external resources outside your control: time­consuming database queries.  An existing application has correct functionality but is unresponsive at times.   Challenges of Java concurrency Experienced programmer Ned Batchelder recently quipped Some people.com/javaworld/jw-06-2012/120626-modern-threading. As I already mentioned. or networked responses with uncontrollable latency." and then two they hav erpoblesms. rather than being directly related to T h r e a d . Most T h r e a d ­based source does not include a s l e e p ( ) ; the purpose of s l e e p ( )  here is to model.javaworld.cgi?pagetosend=/export/home/httpd/javaworld/javaworld/jw-06-2012/120626-modern-t… 4/10 . think. 3.   This is all about speed. then multithreading can help the program complete faster. then it was built with concurrency in mind.) In your own programming.  A computationally­intense application could make better use of multicore hosts.com/cgi-bin/mailto/x_java. for instance." Something else to notice in Listing 1 is that T h r e a d  is an abstract class. "I know. To implement each driver or bee as a T h r e a d ­related object might be convenient from a programming standpoint.

 strict determinacy. It's easy for a newcomer to multithreading to c l o s e ( )  a file handle in one T h r e a d before a different T h r e a d  has finished everything it needs to write. program code can create an object. www. As explained by Jeff Friesen in his 2002 introduction to threading. That feature was multithreading. Testing concurrent programs Ten years ago on JavaWorld.javaworld. The correct starting point to resolving the intrinsic difficulties of concurrent programming was well stated by Heinz Kabutz in his Java Specialist newsletter: recognize that concurrency is a topic that you should understand and study it systematically. That signature is identical to T h r e a d 's r u n ( )  method signature and serves as a thread's entry of execution. who are trained to think in terms of reproducible results. the R u n n a b l e  interface is made for situations where subclassing T h r e a d  isn't possible: The R u n n a b l e  interface declares a single method signature: v o i dr u n ( ) . There are of course tools such as diagramming techniques and formal languages that will help. you must create a runnable to take advantage of multithreading. Different threads might not only produce results in different orders. there will be an impact on how effectively you can test your threaded code. So for those classes that cannot extend T h r e a d .javaworld. learn as much as you can about threading fundamentals like these: Synchronization and immutable objects Thread scheduling and wait/notify Race conditions and deadlock Thread monitors for exclusive access. which has consequences for multithreading coding. this simply wouldn't work. but they can contend at more essential levels for results. To this point.cgi?pagetosend=/export/home/httpd/javaworld/javaworld/jw-06-2012/120626-modern-t… 5/10 . Because R u n n a b l e  is an interface. Fortunately.html troubling to programmers.com/javaworld/jw-06-2012/120626-modern-threading. . in the form of the R u n n a b l e  interface. But the first step is to sharpen your intuition by practicing with simple programs like F i r s t T h r e a d i n g E x a m p l e  in Listing 1. Dave Dyer noted that the Java language had one feature so "pervasively used incorrectly" that he ranked it as a serious design flaw. and invariant sequence. there's a classical solution for the problem. I have only described a use for T h r e a d  that was based on subclasses with an overridden r u n ( ) . Dyer's comment highlights the challenge of testing multithreaded programs. and assertions JUnit best practices ­­ testing multithreaded code   When to use Runnable Object orientation in Java defines singly inherited classes. You cannot simultaneously inherit from R e n d e r e d O b j e c t  or P r o d u c t i o n L i n e  or M e s s a g e Q u e u e  alongside T h r e a d ! This constraint affects many areas of Java. not just multithreading. When you can no longer easily specify the output of a program in terms of a definite sequence of characters. At execution time.5/15/13 http://www. Next.com/cgi-bin/mailto/x_java. In an object design that already involved inheritance. or runnable. conditions. any class can implement that interface by attaching an i m p l e m e n t s  clause to the class header and by providing an appropriate r u n ( )  method. from that class and pass the runnable's reference to an appropriate T h r e a d constructor. It gets worse.

 if you're doing system­level programming and your class is in an is­a relation to T h r e a d . f o r m a t ( " L i n e# % df r o mR e m o t e H o s t' % s ' . \ n " .a f t e r% d m i l l i s e c o n d s . s l e e p ( d e l a y ) . } c o u n t + + .n a m e . MonitorModel i m p o r tj a v a . / /C o n t i n u ei n d e f i n i t e l y .d e l a y ) . n e x t I n t ( 1 0 0 0 ) . R a n d o m .a n d / /i sr e p o r t e dt ot h es c r e e n . Output from MonitorModel . Listing 4 shows three notices from ' e x a m p l e 1 '  in succession: Listing 4. } } } When you run this example. n e wT h r e a d ( t h i s ) . } p u b l i cv o i dr u n ( ){ i n tc o u n t=0 . R a n d o mr=n e wR a n d o m ( ) . An e wv a l u ef r o m / /t h eR e m o t e H o s ta p p e a r se v e r y' d e l a y 'm i l l i s e c o n d s . Listing 3. T h r e a d . it takes only an extra line or two to code using the R u n n a b l e  interface.javaworld. But most application­level use of multithreading relies on composition. c u r r e n t T h r e a d ( ) . s t a r t ( ) . as shown in Listing 3 below. www.a sa n / /a d m i n i s t r a t i v ep r o g r a mm i g h tu s e . i n td e l a y .html Semantically. S y s t e m . } } c l a s sM o n i t o r e d O b j e c t{ } / /T h i sm o d e l sas i m p l em o n i t o ro far e m o t eh o s t .com/javaworld/jw-06-2012/120626-modern-threading. and thus defines a R u n n a b l e  compatible with the application's class diagram. c l a s sM o n i t o r M o d e l{ p u b l i cs t a t i cv o i dm a i n( S t r i n g[ ]a r g s ){ n e wR e m o t e H o s t ( " e x a m p l e1 " ) . c l a s sR e m o t e H o s te x t e n d sM o n i t o r e d O b j e c ti m p l e m e n t sR u n n a b l e{ S t r i n gn a m e .u n t i lak e y b o a r di n t e r r u p t .com/cgi-bin/mailto/x_java. R e m o t e H o s t ( S t r i n gn ){ n a m e=n . then you should subclass directly from T h r e a d . o u t . as is likely to happen in a real­world system monitor. c o u n t . w h i l e( c o u n t + + ){ t r y{ i n td e l a y=r . Output is likely to include "runs" where one R e m o t e H o s t  reports repeatedly while the other is silent. Fortunately. you'll see output from two different threads interleave unpredictably. } c a t c h( I n t e r r u p t e d E x c e p t i o ni e ){ / /T h i sw o u l db eas u r p r i s e . n e wR e m o t e H o s t ( " e x a m p l e2 " ) .cgi?pagetosend=/export/home/httpd/javaworld/javaworld/jw-06-2012/120626-modern-t… 6/10 .5/15/13 http://www. u t i l .javaworld.

) About monitors Note that monitor in Listing 3 stems from the vocabulary of system administrators or network operators. Bill Venners explained monitors thus: A monitor is basically a guardian in that it watches over a sequence of code. Each monitor is associated with an object reference.a f t e r8 0 7 m i l l i s e c o n d s . . Worse. you might have heard it said that threads are a bad idea. Using R u n n a b l e  is thus nearly as succinct as directly subclassing from T h r e a d . (Remember that most application code relies on R u n n a b l e  definitions rather than T h r e a d  subclassing. ­­ C. L i n e# 5 4f r o mR e m o t e H o s t' e x a m p l e1 ' .html .com/cgi-bin/mailto/x_java.a f t e r8 2 0 m i l l i s e c o n d s . L i n e# 6 0f r o mR e m o t e H o s t' e x a m p l e2 ' .javaworld. Once it has obtained the lock.cgi?pagetosend=/export/home/httpd/javaworld/javaworld/jw-06-2012/120626-modern-t… 7/10 .. There's no getting around the reality that multithreading is hard. the thread must obtain a lock on the referenced object. . . nor much teamwork required between them.a f t e r1 8 m i l l i s e c o n d s . which I'll discuss shortly.. if only to avoid a multiple­inheritance conflict. L i n e# 5 8f r o mR e m o t e H o s t' e x a m p l e1 ' . with the result that many programmers simply shun thread­based code. Hoare Hoare's words about the complications of coding seems to apply with special force to multithreading. And the other way is to make it so complicated that there are no obvious deficiencies. A programming monitor is one of several mechanisms for managing thread synchronization. Some Java developers try to mitigate the hazards of thread programming by trading it in for newer (or just www. When a thread arrives at the first instruction in a block of code that is under the watchful eye of a monitor. however. The examples of multithreading to this point have involved little coordination between threads; there's been no particular consequence for one thread executing before another..javaworld. making sure that only one thread at a time executes the code . L i n e# 5 5f r o mR e m o t e H o s t' e x a m p l e1 ' . prone to difficulty. good programmers make them better There are two ways of constructing a software design.   When good threads go bad .5/15/13 http://www.a f t e r8 7 5 m i l l i s e c o n d s .com/javaworld/jw-06-2012/120626-modern-threading. Coincidentally.. . L i n e# 5 6f r o mR e m o t e H o s t' e x a m p l e1 ' . especially as software systems evolve onto multicore architectures. One way is to make it so simple that there are obviously no deficiencies.A. who monitor objects ­­ often physical ones ­­ for which they're responsible.a f t e r9 6 4 m i l l i s e c o n d s . The thread is not allowed to execute the code until it obtains the lock. meaning. Locking is another concept. L i n e# 5 9f r o mR e m o t e H o s t' e x a m p l e2 ' .R. Most of us can't do without the responsiveness and performance improvements of concurrency. programming theory applies the same word to a specific concept in concurrency. L i n e# 5 7f r o mR e m o t e H o s t' e x a m p l e1 ' .a f t e r2 6 1 m i l l i s e c o n d s .a f t e r5 2 5 m i l l i s e c o n d s . the thread enters the block of protected code. In a classic JavaWorld article from 1997.

javaworld.   What's new in Java 6 and 7 The basics of multithreading have changed remarkably little since the early days of the Java platform. but the precise results of "edge cases": does the program behave correctly when a limit is reached? When within one unit of the limit? In the same way.5/15/13 http://www. "Loose coupling" here has several aspects: Share as little state as possible Minimize side­effects and emphasize immutable objects Use standard design patterns (like Subscribe­Publish) to manage shared resources Minimize inter­thread coordination Simplify the lifespans of threads Write testable code Make your threads testable. you'll need to learn not only how to launch runnables. construct your multithreaded application to exercise threads under reproducible.) Know what your favorite debugger offers and be sure that it compares well with the alternatives. In the early years of Java. As your applications grow more sophisticated. design your R u n n a b l e s to be as simple and loosely coupled as possible. Loose coupling If you are going to stick with the Java Threads API and j a v a .html different) styles. started in either order; with more threads than the test host has cores; with controllable simulated loads; and so on. learn how to debug multithreaded applications. which come baked into Java 7. Message­passing models expressed in terms of actors and agents eliminate many of the difficulties of shared state and synchronization. With a little effort and the well­established design techniques discussed below you can make your multithreaded code as understandable and reliable as any other Java source. Debug your code Next. and manage thread lifetimes. salient conditions: with only a single worker thread operating; with two threads. standalone utilities to help with debugging of thread problems were popular; more recently. (For instance.com/cgi-bin/mailto/x_java. supports introspection on threads. the leading debuggers have incorporated essentially all of this functionality. c o n c u r r e n t . A conventional application might build in facilities to ensure not just code coverage. communicate between threads. there are tools. Studying best practices for debugging will improve your code measurably. u t i l . including j d b . So. You know to minimize use of g o t o and global variables; in much the same way. you might prefer to work with the standard Java threading architecture. One www. nearly every debugger. in a different way. what can you do to reduce the hazards of multithreading? First. Still. The popularity of these alternative concurrency models in Java­related languages like Clojure has amplified their familiarity in the Java community proper. but also to synchronize thread execution.javaworld. develop your own sense of functional style.com/javaworld/jw-06-2012/120626-modern-threading. do closures. FInally.cgi?pagetosend=/export/home/httpd/javaworld/javaworld/jw-06-2012/120626-modern-t… 8/10 . See Esther Schindler's "Learning and improving your debugging skills" for a collection of valuable tips and exercises that will improve your Java debugging techniques.

 however.com/javaworld/jw-06-2012/120626-modern-threading. it releases the lock to the debit thread. This generally involves simple synchronization models. u t i l . in the example of the last paragraph ­­ locks the account balance. and teams that carefully review each other's source. we saw a different kind of improvement to thread programming: removal! One common application of multithreading has been to answer questions such as. the j a v a .   Conclusion: What's next for threads Current public plans for Java 8 and Java 9 appear to have little direct impact on threaded programming. it's easy to end up with a final balance of $120 or $90 rather than the correct combination of $110. small and understandable class definitions. A major goal of Java 8. Closures also reduced the pressure to code with threads. In the case of an accounting program. when Java 6 was released at the end of 2006 it brought considerable attention to locking. the other to credit $20. though. At that point.javaworld. sets the balance to $120. (See "Do Java 6 threading optimizations actually work?" for more about threading optimization in Java 6. c o n c u r r e n t techniques. is that multithreading has become easier in several important regards. or to update a bank account balance. www. C a l l a b l e  closures will encourage their use (under the formal language label of "lambda") as an alternative to multithreading. One thread ­­ the credit one. One of the goals for Java 9 is "massive multicore" compatibility.html fortunate change. is more effective use of multicore environments. Some teams do best with new j a v a . For instance. "is there a new file in this FTP repository?" or "have any results been appended to this logfile lately?" Java 7 includes a filesystem monitor and support for asynchronous input/output in j a v a . c o n c u r r e n t  package includes support for writing concurrent loops (Neal Gafter. The result: the speed of multithreaded applications generally improved. Locking is an important technique because threads sometimes contend for resources: two different threads might need to update a global variable that tracks how many users are active. the best results come from using threads.) With the release of Java 7 in the summer of 2011.com/cgi-bin/mailto/x_java. an emphasis on testability. It's very bad. u t i l .5/15/13 http://www. It's not yet clear how this will be effected at the level of language syntax. but using them with the wisdom of the last decade. or even some of the alternative models I've mentioned. scheduled for mid­2013. sometimes dramatically.javaworld. which itself locks the balance. Improvements to Java 6 and the JVMs that implement locks improved performance and refined programmatic control over them. though. One thread tries to debit $10. 2006). say. imagine a starting balance of $100. n i o . thus eliminating one need for programmers to write multithreaded source. until it has successfully updated the balance to the correct sum of $110. For many teams. Expansion of j a v a . and occasionally they're simply wrong. completes its operation. So the n i o  API allows us to directly code file update functionality. c o n c u r r e n t . to allow the threads to interfere with each other. u t i l . One of the most interesting conclusions of the last fifteen years of Java concurrency programming is that threads are not so much to be avoided as handled with respect.cgi?pagetosend=/export/home/httpd/javaworld/javaworld/jw-06-2012/120626-modern-t… 9/10 . Software applications need to guarantee correct results. Depending on the exact sequence of operations. f i l e . The most common way to achieve this is with locks or other means of synchronizing the access of different threads. Perils of locking Locks have the reputation of being difficult and costly. First.

html About the author Cameron Laird began writing Java code before it was called Java and has contributed occasionally to JavaWorld in the years since then.com/javaworld/jw-06-2012/120626-modern-threading. Read more about Core Java in JavaWorld's Core Java section.5/15/13 http://www. http://www.com www.javaworld.com/cgi-bin/mailto/x_java. Keep up with his coding and writing through Twitter as @Phaseit. All contents copyright 1995­2013 Java World. Inc.cgi?pagetosend=/export/home/httpd/javaworld/javaworld/jw-06-2012/120626-modern… 10/10 .javaworld.javaworld.

Sign up to vote on this title
UsefulNot useful