Putting It All Together |
Two objects in the Game application can be accessed by concurrently running threads: theRingMaster
and theRoster
. TheRingMaster
is the most central class of the whole Game application. Its methods can be called from many different threads: theGamesThread
, theBallAnnouncer
thread, and indirectly by any number of Player threads running in different VMs. Similarly, theRoster
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
andRoster
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 theRingMaster
and theRoster
within synchronized code segments.The synchronized code segments in the Game application appear in two forms. First, the
RingMaster
contains manysynchronized
methods. Second, theRegistrarImpl
class contains twosynchronized
blocks: one that synchronizesRingMaster
and the other synchronizes onRoster
.RingMaster's Synchronized Methods
Many ofRingMaster
's methods are marked with thesynchronized
keyword. Among them are the following two methods. The first changes the current "game state" and the other returns it: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 callssynchronized boolean gameInProgress() { return (state == PLAYING || state == CHECKINGFORWINNER) ? true : false; } synchronized void setGameResumed() { if (state == CHECKINGFORWINNER) state = PLAYING; }gameInProgress
while the other simultaneously calls code>setGameResumed. Assume thatstate
isCHECKINGFORWINNER
when the first thread callsgameInProgress
. The first half of theif
statement in thegameInProgress
completes and evaluates tofalse
becausestate
is notPLAYING
, it'sCHECKINGFORWINNER
. At this point, the second thread callssetGameResumed
which completes and sets thestate
toPLAYING
. Now the first thread resumes and completes the second half of theif
statement which also evaluates tofalse
. In this scenario, thegameInProgress
inaccurately returnsfalse
. 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 returnstrue
) beforesetGameResumed
is allowed to begin. Thus the problem encountered above is prevented.The list of methods in
RingMaster
that aren'tsynchronized
is as interesting as those that are. The following methods inRingMaster
are similar to the two shown previously in that they get or set game state. However, these two methods are notsynchronized
:Usingboolean isCheckingForWinner() { return (state == CHECKINGFORWINNER) ? true : false; } void setCheckingForWinner() { state = CHECKINGFORWINNER; }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 accessstate
once so even if two threads do call these two methods simulataneously the results are still valid.
Try This: Look at the other methods inRingMaster
and see if you can figure out why each method issynchronized
or not.
RegistrarImpl's Synchronized Blocks
TheRegistrarImpl
class contains twosynchronized
blocks. One that synchronizes onRingMaster
and one that synchronizes onRoster
. 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 theRingMaster
or theRoster
in incompatible ways.The
mayIPlay
method, shown below, changes theRoster
by adding a new player to the list. The bold code can modify theRoster
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 onRoster
.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]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; } }
RegistrarImpl
contains another synchronized block in itsBINGO
method which is called by a Player to claim a winning card. The synchronized block in this method synchronizes onRingMaster
. 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]
Putting It All Together |