Previous | Next | Trail Map | Putting It All Together--The BINGO Game | Putting It All Together


Using Synchronized Code Segments

Two objects in the Game application can be accessed by concurrently running threads: the RingMaster and the Roster. The RingMaster is the most central class of the whole Game application. Its methods can be called from many different threads: the GamesThread, the BallAnnouncer thread, and indirectly by any number of Player threads running in different VMs. Similarly, the Roster object, which contains a list of all players registered for the current game, can be modified by RMI calls made from any of the Player threads running in different VMs.

The Game application must take precautions to ensure that method calls on RingMaster and Roster are "thread-safe". That is, the Game must ensure that one thread cannot modify one of these objects while another thread is inspecting or modifying it. For instance, the Game must ensure that registration for one player is completed before the registration for another begins, otherwise two players may end up with the same ID or worse. The Game ensures thread-safety by enclosing accesses on the RingMaster and the Roster within synchronized code segments.

The synchronized code segments in the Game application appear in two forms. First, the RingMaster contains many synchronized methods. Second, the RegistrarImpl class contains two synchronized blocks: one that synchronizes RingMaster and the other synchronizes on Roster.

RingMaster's Synchronized Methods

Many of RingMaster's methods are marked with the synchronized keyword. Among them are the following two methods. The first changes the current "game state" and the other returns it:
synchronized boolean gameInProgress() {
    return (state == PLAYING || state == CHECKINGFORWINNER) ? true : false;
}
synchronized void setGameResumed() {
    if (state == CHECKINGFORWINNER)
        state = PLAYING;
}
You can understand why these methods are synchronized by analyzing the following scenario assuming that the two methods involved aren't synchronized. This scenario involves two threads running concurrently one calls gameInProgress while the other simultaneously calls code>setGameResumed. Assume that state is CHECKINGFORWINNER when the first thread calls gameInProgress. The first half of the if statement in the gameInProgress completes and evaluates to false because state is not PLAYING, it's CHECKINGFORWINNER. At this point, the second thread calls setGameResumed which completes and sets the state to PLAYING. Now the first thread resumes and completes the second half of the if statement which also evaluates to false. In this scenario, the gameInProgress inaccurately returns false. So, as you can see, it's possible to create a situation where one thread thinks that the game is over and another thread thinks that the game is still in progress. This could create a BINGO mess.

By synchronizing these two methods, the Java platform ensures that the first method call completes before the second can begin. So in the above scenario, gameInProgress fully completes (and returns true) before setGameResumed is allowed to begin. Thus the problem encountered above is prevented.

The list of methods in RingMaster that aren't synchronized is as interesting as those that are. The following methods in RingMaster are similar to the two shown previously in that they get or set game state. However, these two methods are not synchronized:

boolean isCheckingForWinner() {
    return (state == CHECKINGFORWINNER) ? true : false;
}
void setCheckingForWinner() {
    state = CHECKINGFORWINNER;
}
Using synchronized has an impact on performance, thus you should only use it when necessary. It's not necessary to synchronize these two methods because they access state once so even if two threads do call these two methods simulataneously the results are still valid.


Try This: Look at the other methods in RingMaster and see if you can figure out why each method is synchronized or not.

RegistrarImpl's Synchronized Blocks

The RegistrarImpl class contains two synchronized blocks. One that synchronizes on RingMaster and one that synchronizes on Roster. Both of these blocks are within remote methods--methods in the Game that are called remotely from the Player application--and ensure that multiple Players calling these methods concurrently do not change the RingMaster or the Roster in incompatible ways.

The mayIPlay method, shown below, changes the Roster by adding a new player to the list. The bold code can modify the Roster object so it can only be executing in one thread at a time. Thus it is encapsulated within a block of code that is synchronized on Roster.

public Ticket mayIPlay(String playerName, int numCards, long seed)
                               throws RemoteException
{

    if (!ringMaster.ready())
        return new Ticket("BINGO server not ready. You can't play.");
    else if (!ringMaster.isRegistering())
        return new Ticket("Registration not open. You can't play.");

    if (numCards > gameParameters.getMaxCards())
        numCards = gameParameters.getMaxCards();

    synchronized (roster) {
        if (roster.size() == gameParameters.getMaxPlayers())
            return new Ticket("Game full. You can't play.");

        Card[] cards = new Card[numCards];
        Random generator = new Random(seed);

        for (int i = 0; i < numCards; i ++)
            cards[i] = new Card(generator);

        ringMaster.signTheCards(cards);

        PlayerRecord p = new PlayerRecord(roster.nextPlayerID(), playerName, numCards);
        String welcomeMessage = "Welcome to game # " + ringMaster.getGameNumber() + ".";
        Ticket ticket = new Ticket(welcomeMessage, p.ID, cards);
        roster.addElement(p, ringMaster);
        return ticket;
    }
}
The Java runtime ensures that only one thread can access the synchronized object at a time. So when one thread enters the synchronized block, accesses to that object by other threads are blocked until the original thread exits the synchronized block. [PENDING: verify previous statement, and figure out if synch'ed blocks block out all method calls on the object or just synch'ed ones]

RegistrarImpl contains another synchronized block in its BINGO method which is called by a Player to claim a winning card. The synchronized block in this method synchronizes on RingMaster. The code within this block modifies and checks the game state several times within this block and must halt the rest of the game while verifying the BINGO claim.

public Answer BINGO(int playerID, Card c) throws RemoteException {

    PlayerRecord p = roster.searchForPlayerWithID(playerID);

    if (p == null)
        return new Answer(false, "Can't find player with ID: " + playerID + ".");

    if (p.wolfCries >= MAX_WOLF_CRIES)
        return new Answer(false, "Sorry, wolf cryer, you're out of the game.");

    synchronized (ringMaster) {

        ringMaster.setCheckingForWinner();

        if (ringMaster.verify(c)) {

            ringMaster.setGameOver();
            return new Answer(true, "You won! Congratulations!");

        } else {

            p.wolfCries++;
            ringMaster.setGameResumed();
            ringMaster.sendPlayerStatusMessage(p);
            if (p.wolfCries == MAX_WOLF_CRIES) {
                return new Answer(false, "You've cried wolf 3 times. You're out.");
            } else {
                return new Answer(false, "You cried wolf..." +
                                  (MAX_WOLF_CRIES - p.wolfCries) +
                                  " more and you're out.");
            }
        }
    } 
}

Synchronized Block or Synchronized Method

[PENDING: this section is under construction]


Previous | Next | Trail Map | Putting It All Together--The BINGO Game | Putting It All Together