Recipe 24.5 Synchronizing Threads with the synchronized Keyword


Problem

You need to protect certain data from access by multiple threads.

Solution

Use the synchronized keyword on the method or code you wish to protect.

Discussion

I discussed the synchronized keyword briefly in Recipe 17.4. This keyword specifies that only one thread at a time is allowed to run the given method in a given object (for static methods, only one thread is allowed to run the method at a time). 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 Recipe 7.3) are synchronized.[4] 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.

[4] 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).

Bear in mind that threads can be interrupted at almost any time, in which case control is 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 uninitialized reference in the array. This state of affairs is shown in Figure 24-2.

Figure 24-2. Nonthreadsafe add method in operation: normal and failed updates
figs/jcb2_2402.gif


Now you might think, "No problem, I'll just combine lines 2 and 3":

data[max++] = obj;

As the game 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 unnamed 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-9. 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-9 is a web servlet that I wrote for use in the classroom, following a suggestion from Scott Weingust (scottw@sysoft.ca). It lets you play a quiz show game of the style where the host asks a question and the first person to press her 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-9. 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>");     } }

Two HTML pages 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.

Figure 24-3. BuzzInServlet in action
figs/jcb2_2403.gif


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 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 (Recipe 7.7) or a servlet initialization parameter (as described in O'Reilly's Java Servlet Programming):

<html><head><title>Reset Buzzer</title></head> <body> <h1>Display Winner</h1> <p> <b>The winner is:</b> <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.

Figure 24-4. BuzzInServlet game show host function
figs/jcb2_2404.gif


For a more complete game, of course, the servlet would keep a Stack (Recipe 7.14) 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.



Java Cookbook
Java Cookbook, Second Edition
ISBN: 0596007019
EAN: 2147483647
Year: 2003
Pages: 409
Authors: Ian F Darwin

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net