Wednesday, 19 September 2012

Puzzle 76: Ping Pong


This program consists entirely of synchronized static methods. What does it print? Is it guaranteed to print the same thing every time you run it?
public class PingPong {

    public static synchronized void main(String[] a) {

        Thread t = new Thread() {

            public void run() { pong(); }

        };

        t.run();

        System.out.print("Ping");

    }



    static synchronized void pong() {

        System.out.print("Pong");

    }

}


Solution 76: Ping Pong

In a multithreaded program, it is generally a good bet that the behavior can vary from run to run, but this program always prints the same thing. Before a synchronized static method executes, it obtains the monitor lock associated with its Class object [JLS 8.4.3.6]. Therefore, the main thread acquires the lock on PingPong.class before creating the second thread. As long as the main thread holds on to this lock, the second thread can't execute a synchronized static method. In particular, the second thread can't execute the pong method until the main method prints Ping and completes execution. Only then does the main thread relinquish the lock, allowing the second thread to acquire it and print Pong. This analysis leaves little doubt that the program should always print PingPong. There is one small problem: If you tried the program, you found that it always prints PongPing. What on earth is going on?
Strange as it may seem, this is not a multithreaded program. Not a multithreaded program? How can that be? Surely it creates a second thread. Well, yes, it does create a second thread, but it never starts that thread. Instead, the main thread simply invokes the run method of the new THRead instance, and the run method executes synchronously in the main thread. Because a thread is allowed to acquire the same lock repeatedly [JLS 17.1], the main thread is permitted to reacquire the lock on PingPong.class when the run method invokes the pong method. The pong method prints Pong and returns to the run method, which returns to the main method. Finally, the main method prints Ping, which explains the program's output.
Fixing the program is as simple as changing t.run to t.start. Once this is done, the program reliably prints PingPong as expected.
The lesson is simple: Be careful not to invoke a thread's run method when you mean to invoke its start method. Unfortunately, this mistake is all too common, and it can be very difficult to spot. Perhaps the main lesson of this puzzle is for API designers: If Thread didn't have a public run method, it would be impossible for programmers to invoke it accidentally. The Thread class has a public run method because it implements Runnable, but it didn't have to be that way. An alternative design would be for each THRead instance to encapsulate a Runnable, giving rise to composition in place of interface inheritance. As discussed in Puzzle 47, composition is generally preferable to inheritance. This puzzle demonstrates that the principle holds even for interface inheritance.

No comments:

Post a Comment

Your comments are welcome!