In main, we're calling the static createThread method, passing an int and a Thread. However, the Thread we're passing must first be returned by a separate call to createThread. So our first call is createThread( 20, Thread.currentThread() ), passing int 20 to local variable i, and the currentThread to local variable t1. Inside createThread, a new Thread called t2 is created (with run() overridden in its anonymous class body). Now we println i+3, which is 23.
Next, we call start() on t2, which will eventually call t2's run() method. But the point at which this happens depends on the scheduler -- which is platform dependent. Note that i is final within this method, so its value is copied (inlined) into the anonymous class definition of t2. Therefore, when t2 does run, we'll see a println of 21 (i+1).
But (based on my output) t2 doesn't get to run just yet. Instead, we println i+4, which is 24. Next, t2 is returned to our original method call, where it is used as an argument in our next call to createThread. Note that we're now exiting the createThread method, so its local variables no longer exist.
Now that our first call to createThread has returned the Thread object we need, we call createThread( 10, t2 ), passing int 10 to local variable i, and our returned t2 to local variable t1. A second t2 is created. Next, we println i+3, which is 13. Now we call start() on the second t2, but again must wait for the scheduler to call its run() method.
So we println i+4, which is 14. Then we return our second t2. (Nothing is done with this returned thread, although it does signal our exiting the method. And, with this, our call in main() has completed.)
So at this point, we've got two threads waiting to run: our first t2 (in which i=20), and our second t2 (in which i=10). The scheduler evidentially picks the first to run, since we println i+1 = 21 (which, as described above, is our signal that our first t2 is running).
Next, there's a statement for t1 (which was also inlined as a final variable) to call its join() method. According to the API, the join() method "waits for the current thread to die" and throws an InterruptedException "if another thread has interrupted the current thread." As you'll recall, t1 was the thread that was executing when we first entered the createThread method, and that thread is now dead. The exception is thrown, and in the catch block we println i+2, which is 22. At this point, our first t2's run method is done, and that thread dies.
So now our second t2 runs, and we println i+1 = 11. We again try to call t1.join(). In this instance, t1 was inlined to represent our first version of t2, which has also died. So an exception is thrown, and we println i+2 = 12 in the catch block.
NOTE: If you insert System.out.println("t1 alive: " + t1.isAlive()); just prior to the t1.join(); line, you'll see that t1 is already dead in both cases. I'm not familiar with the join() method, but this seems to be why the exception is thrown. I would like to see a good example of how join() is supposed to work. [ September 17, 2004: Message edited by: marc weber ]
"We're kind of on the level of crossword puzzle writers... And no one ever goes to them and gives them an award." ~Joe Strummer sscce.org