|
|
Start of Tutorial > Start of Trail > Start of Lesson |
Search
Feedback Form |
Two objects in the Game application can be accessed by concurrently running threads: theRingMasterand the
Roster. The
RingMasteris the most central class of the whole Game application. Its methods can be called from many different threads: theGamesThread, theBallAnnouncerthread, and indirectly by any number of Player threads running in different VMs. Similarly, theRosterobject, 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
RingMasterandRosterare "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 theRingMasterand theRosterwithin synchronized code segments.The synchronized code segments in the Game application appear in two forms. First, the
RingMastercontains manysynchronizedmethods. Second, theRegistrarImplclass contains twosynchronizedblocks: one that synchronizesRingMasterand the other synchronizes onRoster.
Many ofRingMaster's methods are marked with thesynchronizedkeyword. 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; }gameInProgresswhile the other simultaneously calls code>setGameResumed. Assume thatstateisCHECKINGFORWINNERwhen the first thread callsgameInProgress. The first half of theifstatement in thegameInProgresscompletes and evaluates tofalsebecausestateis notPLAYING, it'sCHECKINGFORWINNER. At this point, the second thread callssetGameResumedwhich completes and sets thestatetoPLAYING. Now the first thread resumes and completes the second half of theifstatement which also evaluates tofalse. In this scenario, thegameInProgressinaccurately 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,
gameInProgressfully completes (and returnstrue) beforesetGameResumedis allowed to begin. Thus the problem encountered above is prevented.The list of methods in
RingMasterthat aren'tsynchronizedis as interesting as those that are. The following methods inRingMasterare 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; }synchronizedhas an impact on performance, thus you should only use it when necessary. It's not necessary to synchronize these two methods because they accessstateonce so even if two threads do call these two methods simulataneously the results are still valid.
Try This: Look at the other methods inRingMasterand see if you can figure out why each method issynchronizedor not.
TheRegistrarImplclass contains two
synchronizedblocks. One that synchronizes onRingMasterand 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 theRingMasteror theRosterin incompatible ways.The
mayIPlaymethod, shown below, changes theRosterby adding a new player to the list. The bold code can modify theRosterobject 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; } }
RegistrarImplcontains another synchronized block in itsBINGOmethod 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."); } } } }
[PENDING: this section is under construction]
|
|
Start of Tutorial > Start of Trail > Start of Lesson |
Search
Feedback Form |
Copyright 1995-2004 Sun Microsystems, Inc. All rights reserved.