I discussed the
synchronized
keyword briefly in Section 16.5. This keyword specifies that only one thread
at a time is allowed to run the given code. You can synchronize
methods or smaller blocks of code. It is easier and safer to
synchronize entire
methods,
but this can be more costly in terms of blocking threads that could
run. Simply add the synchronized
keyword on the
method. For example, many of the methods of
Vector
(see Section 7.4)
are synchronized.[57] This ensures
that the vector does not become corrupted or give incorrect results
when two threads update or retrieve from it at the same time.
Bear in mind that threads can be interrupted at almost any time and
control given to another thread. Consider the case of two threads
appending to a data structure at the same time. Let’s suppose
we have the same methods as Vector
, but
we’re operating on
a simple array. The add( )
method simply uses the current number of objects as an
array index, then increments it:
1 public void add(Object obj) { 2 data[max] = obj; 3 max = max + 1; 4 }
Threads A and B both wish to call this method. Now suppose that
Thread A gets interrupted after line 2 but before line 3, and then
Thread B gets to run. Thread B does line 2, overwriting the contents
of data[max]
; we’ve now lost all reference
to the object that Thread A passed in! Thread B then increments
max
in line 3, and returns. Later, Thread A gets
to run again; it resumes at line 3 and increments
max
past the last valid object. So not only have
we lost an object, but we have an un-initialized reference in the
array. This state of affairs is shown in Figure 24-2.
Now you might think, “No problem, I’ll just combine lines 2 and 3”:
data[max++] = obj;
As the talk show host sometimes says, “Bzzzzt! Thanks for playing!” This change makes the code a bit shorter, but has absolutely no effect on reliability. Interrupts don’t happen conveniently on Java statement boundaries; they can happen between any of the many JVM machine instructions that correspond to your program. The code can still be interrupted after the store and before the increment. The only good solution is to use Java synchronization.
Making the method
synchronized
means that
any invocations of it will wait
if one thread has already started running the method:
public synchronized void add(Object obj) { ... }
Anytime you wish to synchronize some code, but not an entire method,
use the
synchronized
keyword on an un-named code block within a
method, as in:
synchronized (someObject) { // this code will execute in one thread at a time }
The choice of object is up to you. Sometimes it makes sense to
synchronize on the object containing the code, as in Example 24-8. For synchronizing access to an
ArrayList
, it would make sense to use the
ArrayList
instance, as in:
synchronized(myArrayList) { if (myArrayList.indexof(someObject) != -1) { // do something with it. } // else create an object and add it... }
Example 24-8 is a servlet (see Section 18.2) that I wrote for use in the classroom,
following a suggestion from Scott Weingust
([email protected]). It lets you
play a quiz show game of the style where the host asks a question and
the first person to press their buzzer (buzz in) gets to try to
answer the question correctly. To ensure against having two people
buzz in simultaneously, the code uses a synchronized block around the
code that updates the boolean
buzzed
variable. And for reliability, any code
that accesses this boolean is
also synchronized.
Example 24-8. BuzzInServlet.java
import javax.servlet.*; import javax.servlet.http.*; import java.io.*; /** A quiz-show "buzzer" servlet: the first respondent wins the chance * to answer the skill-testing question. Correct operation depends on * running in a Servlet container that CORRECTLY implements the Servlet * spec, that is, a SINGLE INSTANCE of this servlet class exists, and it * is run in a thread pool. This class does not implement "SingleThreadModel" * so a correct Servlet implementation will use a single instance. * <p> * If you needed to work differently, you could synchronize on an object * stored in the Servlet Application Context, at a slight increased cost * in terms of system overhead. */ public class BuzzInServlet extends HttpServlet { /** This controls the access */ protected static boolean buzzed = false; /** who got the buzz? */ protected static String winner; /** doGet is called from the contestants web page. * Uses a synchronized code block to ensure that * only one contestant can change the state of "buzzed". */ public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { boolean igotit = false; // Do the synchronized stuff first, and all in one place. synchronized(this) { if (!buzzed) { igotit = buzzed = true; winner = request.getRemoteHost() + '/' + request.getRemoteAddr( ); } } response.setContentType("text/html"); PrintWriter out = response.getWriter( ); out.println("<html><head><title>Thanks for playing</title></head>"); out.println("<body bgcolor="white">"); if (igotit) { out.println("<b>YOU GOT IT</b>"); getServletContext( ).log("BuzzInServlet: WINNER " + request.getRemoteUser( )); // TODO - output HTML to play a sound file :-) } else { out.println("Thanks for playing, " + request.getRemoteAddr( )); out.println(", but " + winner + " buzzed in first"); } out.println("</body></html>"); } /** The Post method is used from an Administrator page (which should * only be installed in the instructor/host's localweb directory). * Post is used for administrative functions: * 1) to display the winner; * 2) to reset the buzzer for the next question. * <p> * In real life the password would come from a Servlet Parameter * or a configuration file, instead of being hardcoded in an "if". */ public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter( ); if (request.getParameter("password").equals("syzzy")) { out.println("<html><head><title>Welcome back, host</title><head>"); out.println("<body bgcolor="white">"); String command = request.getParameter("command"); if (command.equals("reset")) { // Synchronize what you need, no more, no less. synchronized(this) { buzzed = false; winner = null; } out.println("RESET"); } else if (command.equals("show")) { synchronized(this) { out.println("<b>Winner is: </b>" + winner); } } else { out.println("<html><head><title>ERROR</title><head>"); out.println("<body bgcolor="white">"); out.println("ERROR: Command " + command + " invalid."); } } else { out.println("<html><head><title>Nice try, but... </title><head>"); out.println("<body bgcolor="white">"); out.println( "Your paltry attempts to breach security are rebuffed!"); } out.println("</body></html>"); } }
There
are
two
HTML pages that lead to the
servlet. The contestant’s page simply has a large link
(<a
href=/servlet/BuzzInServlet>
). Anchor links
generate an HTML GET, so the servlet engine calls
doGet( )
.
<html><head><title>Buzz In!</title></head> <body> <h1>Buzz In!</h1> <p> <font size=+6> <a href="servlet/BuzzInServlet"> Press here to buzz in! </a> </font>
The HTML is pretty plain, but it does the job. Figure 24-3 shows the look and feel.
The game show host has access to an HTML form with a
POST method, which calls the
doPost( )
method. This displays the
winner to the game show host, and also resets the
“buzzer” for the next question. A
password is provided;
it’s hardcoded here, but in reality the password would come
from a properties file (Section 7.8) or a servlet
initialization parameter (see the Java Servlet
Programming book).
<html><head><title>Reset Buzzer</title></head> <body> <h1>Display Winner</h1> <p> <b>The winner is: <form method="post" action="servlet/BuzzInServlet"> <input type="hidden" name="command" value="show"> <input type="hidden" name="password" value="syzzy"> <input type="submit" name="Show" value="Show"> </form> <h1>Reset Buzzer</h1> <p> <b>Remember to RESET before you ask the contestants each question!</b> <form method="post" action="servlet/BuzzInServlet"> <input type="hidden" name="command" value="reset"> <input type="hidden" name="password" value="syzzy"> <input type="submit" name="Reset" value="RESET!"> </form>
The game show host functionality is shown in Figure 24-4.
For a more complete game, of course, the servlet would keep a
Stack
(Section 7.16) of people in
the order they buzzed in, in case the first person doesn’t
answer the question correctly. Access to this would have to be
synchronized too, of course.
[57] The corresponding methods of
ArrayList
are not synchronized; this
makes nonthreaded use of an ArrayList
about 20 to
30 percent faster (see Recipe 7.17).
3.145.35.247