All the "threading magic" is inherited from the Thread base class (line 1). The ThreadPingPong derived class overrides the base class's run() method with its application-specific logic (lines 11-16). It puts itself in the ready state by calling the base class's start() method in its constructor (line 9). main() then instantiates two thread instances (lines 18-19). The run() method will be invoked automatically when the thread scheduler promotes each thread to the running state.
Threads may not run() themselves. Only the JVM's thread scheduler can move one of potentially many ready threads to the running state. The thread scheduler allocates the scarce CPU resource to all competing threads within an application, and oversees all state transitions of each thread.Returning to the previous observation about three distinct parts of a thread (the virtual CPU, the code, and the data); the Thread base class represents the virtual CPU (the abstraction of multiple threads of execution), the ThreadPingPong derived class methods represent the code, and the "identity" of each ThreadPingPong instance (the collective state of all its data members) represent the data.
In the "aggregation" approach, all the "threading magic" is acquired by creating an instance of class Thread and passing the application object to the Thread's constructor (line 30). The application class must implements the Runnable interface (line 22) which specifies a single method - run() (lines 33-38). Just as in the top example, a thread cannot run() itself. It must "register" itself with the thread scheduler by calling start() (line 31). Then when the scheduler moves the thread from the ready state to the running state, the run() method is invoked automatically.