In the golf club example, when a player thread finished playing and returned its golf balls, it simply terminated. In some concurrent programming situations, we may want to determine a result that is computed by a dynamically created thread. Usually, a slave thread is created to perform some input/output (I/O) activity while the master thread that created it continues with some other activity. At some point, the master must synchronize with the slave to retrieve the result of the I/O activity. This master–slave arrangement is frequently used to allow the master thread to continue executing while the slave waits for a remote communication to complete. When the remote communication completes, the slave thread terminates and the master must obtain the result of the remote communication. We could use a monitor to allow the slave to signal when it is about to terminate. Alternatively, the master could continuously test the status of the slave thread using the isAlive() method. This method returns false when the thread to which it is applied terminates. However, this busy wait would consume CPU cycles that could be put to better use by another thread. To avoid this busy wait, the Java Thread class provides a method to allow one thread to await the termination of another:
public final void join() throws InterruptedException
Waits for this thread to die, e.g. by returning from run() or as a result of stop().
Figure 9.8 is the display of an applet that demonstrates the operation of join(). The upper display shows the master executing concurrently with the slave. The slave was created at the point the master’s segment changed color. The bottom display shows the point at which the slave has terminated and the master has obtained the result of its computation. In the demonstration, the result is the amount the slave rotates its display before terminating. The amount of slave rotation can be adjusted by the slider control positioned below its rotating segment. The amount that the master rotates before waiting for the slave to terminate is adjusted using the other slider.
Figure 9.8: join() demonstration applet.
By adjusting the sliders, it is possible to arrange for the master to wait for the slave to terminate or for the slave to terminate before the master gets the result. The code for both Master and Slave threads is depicted in Program 9.6.
Program 9.6: Master and Slave classes.
class Master implements Runnable { ThreadPanel slaveDisplay; SlotCanvas resultDisplay; Master(ThreadPanel tp, SlotCanvas sc) {slaveDisplay=tp; resultDisplay=sc;} public void run() { try { String res=null; while(true) { while (!ThreadPanel.rotate()); if (res!=null) resultDisplay.leave(res); // create new slave thread Slave s = new Slave(); Thread st = slaveDisplay.start(s,false); // continue execution while (ThreadPanel.rotate()); // wait for slave termination st.join(); // get and display result from slave res = String.valueOf(s.result()); resultDisplay.enter(res); } } catch (InterruptedException e){} } } class Slave implements Runnable { int rotations = 0; public void run() { try { while (!ThreadPanel.rotate()) ++rotations; } catch (InterruptedException e){} } int result(){ return rotations; } }
The Slave thread is created and started using the start() method provided by ThreadPanel. This returns a reference to the new thread. The result is obtained from the Slave thread by calling the result() method after the Slave thread has terminated. Note that result() need not be synchronized since, as long as it is only called after termination, there can be no interference between Master and Slave threads.