Activity 8.8 - Testing the three game players

Topic

This activity allows you to explore the difference between notify and notifyAll.

Materials

We have provided a program consisting of the following classes:

Task

The program has been written so that the three players must take turns to roll the dice in strict rotation

player 1, player 2, player 3, player 1, player 2, player 3,...

This is achieved by synchronization code which uses wait and notifyAll. The purpose of the activity is to investigate the effects of making various changes to this code. The two main conclusions to be drawn are given in the notes at the end of the activity.

Instructions

  1. Compile and run the program. You should find that the players always follow the specified order, e.g.

    Player 1 scores 9
    Player 2 scores 3
    Player 3 scores 7
    Player 1 scores 10
    Player 2 scores 3
    Player 3 scores 6
    etc.

    When every player has had 100 goes the program finishes.

  2. First try changing notifyAll to notify in the rollDice method of BoardGame. Now when the program is executed it is possible for all three threads to become simultaneously blocked, in a similar way to the example of the three trains discussed in the unit. This may not occur on every run, and you may need to execute the program a number of times before you see it happen.

    When all the threads become blocked the program will still be executing and you will need to stop it.

  3. Change notify back to notifyAll. Now alter the statement

    while (turn != playerID)

    in rollDice so that it reads

    if (turn != playerID)

    Re-run the program and observe what happens. The sequence is quite erratic, e.g.

    Player 1 scores 9
    Player 2 scores 6
    Player 1 scores 4
    Player 1 scores 4
    Player 2 scores 8
    Player 1 scores 8
    etc

    What's happening here? The explanation is this. The statement

    if (turn != playerID)

    checks to see if the thread is attempting to go out of turn. If so, the thread is blocked. But then later, when another thread calls notify or notifyAll, the blocked thread may become runnable again. It will then resume running at the point immediately following wait. The condition (turn != playerID) is not checked again. So the thread that has just been woken up will carry happily on, oblivious to whether it is going out of turn or not!

    But when we use

    while (turn != playerID)

    then the call to wait is inside the loop body. So when the thread resumes the condition is re-tested, and if it is still not appropriate for this particular thread to proceed it will block again.

    In fact threads should always re-test the condition, because even in cases where it would be safe for a thread to proceed at the moment when it is woken up, the scheduler may perfectly well allow another thread to run first. This other thread can change the data, so that by the time the newly-woken thread runs it may no longer be safe for it to continue after all.

    You may have noticed earlier when you experimented with using if instead of while that the program sometimes froze before all the threads had completed their 100 goes.

    This happens when one of the threads finishes before the others. Suppose it is player 1. Players 2 and 3 are left running on their own. At some future point it may happen that turn gets set to 1, and both player 2 and player 3 test that value and block. Since there are no other threads running there is no way notify can be called, and players 2 and 3 will never be released.

Notes

It's possible to end up with unterminated threads here if your code doesn't work as planned.  Don't forget to stop any threads before moving on to another activity! There are several ways to do this. One is to use the 'run' bar at the bottom of the NetBeans IDE, below the editorand to click on the 'x' symbol next to the bar, as shown in the following screen shot:

Click on the 'x' to stop the run

Another way to stop a build or a run of a program is using the Run menu and the Stop Build/Run option.

If these methods fail, your best option is to kill the java.exe process using your task or process manager. (You run the risk of terminating something you did not intend to if there are several java processes running.)