Coordinating Access to Variables from Threads

   

Java™ 2 Primer Plus
By Steven Haines, Steve Potts

Table of Contents
Chapter 18.  Programming with Threads


Whenever we have two threads running at the same time, we introduce the possibility that unpredictable results will occur if both threads modify the same variable in the class. We saw in the preceding examples that the output of a program can vary in the order that things print when multiple threads are used. For the same reasons, mathematical results can vary also. Listing 18.7 shows an example that has a problem.

Listing 18.7 The TestUnsynchronized.java File
 /*   * TestUnsynchronized.java   *   * Created on September 26, 2002, 11:11 AM   */  package ch18;  /**   *   * @author  Stephen Potts   */  public class TestUnsynchronized  {     int taskID;      /** Creates a new instance of TestUnsynchronized */      public TestUnsynchronized()      {      }      public void performATask(int val)      {          print("entering performATask()");          taskID = val;           print("performATask() variable taskID " + taskID);          try          {              Thread.sleep(4000);          }catch (InterruptedException x){}          print("performATask() woke up taskID " + taskID);          print( "leaving performATask()");       }      public static void print(String msg)      {          String threadName = Thread.currentThread().getName();          System.out.println(threadName + ": " + msg);      }      public static void main(String[] args)       {          final TestUnsynchronized tus = new TestUnsynchronized();          Runnable runA = new Runnable()          {              public void run()              {                  tus.performATask(3);              }          };          Thread ta = new Thread(runA, "threadA");          ta.start();          try          {              Thread.sleep(2000);          }catch (InterruptedException ie){}          Runnable runB = new Runnable()          {              public void run()              {                  tus.performATask(7);               }          };          Thread tb = new Thread(runB, "threadB");          tb.start();      }  } 

We need one variable that both threads can change.

 int taskID; 

We also need a method that both of the threads can call.

 public void performATask(int val)  {      print("entering performATask()");      taskID = val; 

We print the taskID both before and after the thread sleeps for four seconds. This sleep is added to make the problem obvious.

 print("performATask() variable taskID " + taskID); 

In the main method, we need a handle to this class. In addition, we need to declare this variable final because we are going to access it from an anonymous inner class.

 final TestUnsynchronized tus = new TestUnsynchronized(); 

We declare a handle to be of type Runnable. Then, we create the anonymous inner class that it points to.

 Runnable runA = new Runnable()  {      public void run()      {          tus.performATask(3);      }  }; 

Next, we create a thread, pass it the handle, and name it "threadA".

 Thread ta = new Thread(runA, "threadA"); 

Finally, we start it.

 ta.start(); 

We create a second thread in the same fashion and start it.

         Runnable runB = new Runnable()          {              public void run()              {                  tus.performATask(7);              }          };          Thread tb = new Thread(runB, "threadB");          tb.start();      }  } 

The first thread will enter the performATask() method, set the taskID and go to sleep. The second thread will enter the method, set the taskID then sleep also. When the first thread wakes up, it will print the value of the taskID, which will now be 7 instead of 3.

 public void performATask(int val)  {      print("entering performATask()");      taskID = val;      print("performATask() variable taskID " + taskID);      try      {          Thread.sleep(4000);      }catch (InterruptedException x){}      print("performATask() woke up taskID " + taskID);      print( "leaving performATask()");  } 

The problem is that when threadA wakes up, he will think that the taskID is 7 even though this value was set by another thread. The correct behavior is to have each thread process the value it passed in.

 threadA: entering performATask()  threadA: performATask() variable taskID 3  threadB: entering performATask()  threadB: performATask() variable taskID 7  threadA: performATask() woke up taskID 7  threadA: leaving performATask()  threadB: performATask() woke up taskID 7  threadB: leaving performATask() 

We can get that value by adding a modifier to the performATask() method. This improved version is shown here in Listing 18.8.

Listing 18.8 The TestSynchronized.java File
 /*   * TestSynchronized.java   *   * Created on September 26, 2002, 11:11 AM   */  package ch18;  /**   *   * @author  Stephen Potts   */  public class TestSynchronized  {     int taskID;      /** Creates a new instance of TestSynchronized */      public TestSynchronized()      {      }      public synchronized void performATask(int val)      {          print("entering performATask()");          taskID = val;          print("performATask() variable taskID " + taskID);          try           {              Thread.sleep(4000);          }catch (InterruptedException x){}          print("performATask() woke up taskID " + taskID);          print( "leaving performATask()");      }      public static void print(String msg)      {          String threadName = Thread.currentThread().getName();          System.out.println(threadName + ": " + msg);      }      public static void main(String[] args)      {          final TestSynchronized tus = new TestSynchronized();          Runnable runA = new Runnable()          {              public void run()              {                  tus.performATask(3);              }          };          Thread ta = new Thread(runA, "threadA");          ta.start();          try          {              Thread.sleep(2000);          }catch (InterruptedException ie){}          Runnable runB = new Runnable()          {              public void run()              {                  tus.performATask(7);               }          };          Thread tb = new Thread(runB, "threadB");          tb.start();      }  } 

The addition of the word synchronized to the declaration of the method causes each thread to have to obtain a lock on the object before proceeding to run the method.

 public synchronized void performATask(int val) 

The result of running the code with this change is remarkable.

 threadA: entering performATask()  threadA: performATask() variable taskID 3  threadA: performATask() woke up taskID 3  threadA: leaving performATask()  threadB: entering performATask()  threadB: performATask() variable taskID 7  threadB: performATask() woke up taskID 7  threadB: leaving performATask() 

Notice that the processing for threadA completes before the processing for threadB can even start. This is because when threadB attempted to get the exclusive lock, it failed. ThreadB went into a retry loop while threadA was completing its processing of this method. After threadA released the lock, threadB was free to proceed.


       
    Top
     



    Java 2 Primer Plus
    Java 2 Primer Plus
    ISBN: 0672324156
    EAN: 2147483647
    Year: 2001
    Pages: 332

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