The JavaTM Tutorial

| Start of Tutorial > Start of Trail > Start of Lesson | Search

Trail: Putting It All Together
Lesson: BINGO!

## 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]

| Start of Tutorial > Start of Trail > Start of Lesson | Search