Follow Techotopia on Twitter

On-line Guides
All Guides
eBook Store
iOS / Android
Linux for Beginners
Office Productivity
Linux Installation
Linux Security
Linux Utilities
Linux Virtualization
Linux Kernel
System/Network Admin
Programming
Scripting Languages
Development Tools
Web Development
GUI Toolkits/Desktop
Databases
Mail Systems
openSolaris
Eclipse Documentation
Techotopia.com
Virtuatopia.com

How To Guides
Virtualization
General System Admin
Linux Security
Linux Filesystems
Web Servers
Graphics & Desktop
PC Hardware
Windows
Problem Solutions

  




 

 

Thinking in Java
Prev Contents / Index Next

Critical sections

Sometimes, you only want to prevent multiple thread access to part of the code inside a method instead of the entire method. The section of code you want to isolate this way is called a critical section and is also created using the synchronized keyword. Here, synchronized is used to specify the object whose lock is being used to synchronize the enclosed code:

synchronized(syncObject) {
  // This code can be accessed 
  // by only one thread at a time
}


This is also called a synchronized block; before it can be entered, the lock must be acquired on syncObject. If some other thread already has this lock, then the critical section cannot be entered until the lock is given up.

The following example compares both approaches to synchronization by showing how the time available for other threads to access an object is significantly increased by using a synchronized block instead of synchronizing an entire method. In addition, it shows how an unprotected class can be used in a multithreaded situation if it is controlled and protected by another class:

//: c13:CriticalSection.java
// Synchronizing blocks instead of entire methods. Also
// demonstrates protection of a non-thread-safe class
// with a thread-safe one.
import java.util.*;

class Pair { // Not thread-safe
  private int x, y;
  public Pair(int x, int y) {
    this.x = x;
    this.y = y;
  }
  public Pair() { this(0, 0); }
  public int getX() { return x; }
  public int getY() { return y; }
  public void incrementX() { x++; }
  public void incrementY() { y++; }
  public String toString() {
    return "x: " + x + ", y: " + y;
  }
  public class PairValuesNotEqualException
  extends RuntimeException {
    public PairValuesNotEqualException() {
      super("Pair values not equal: " + Pair.this);
    }
  }
  // Arbitrary invariant -- both variables must be equal:
  public void checkState() {
    if(x != y)
      throw new PairValuesNotEqualException();
  }
}

// Protect a Pair inside a thread-safe class:
abstract class PairManager {
  protected Pair p = new Pair();
  private List storage = new ArrayList();
  public synchronized Pair getPair() {
    // Make a copy to keep the original safe:
    return new Pair(p.getX(), p.getY());
  }
  protected void store() { storage.add(getPair()); }
  // A "template method":
  public abstract void doTask();
}

// Synchronize the entire method:
class PairManager1 extends PairManager {
  public synchronized void doTask() {
    p.incrementX();
    p.incrementY();
    store();
  }
}

// Use a critical section:
class PairManager2 extends PairManager {
  public void doTask() {
    synchronized(this) {
      p.incrementX();
      p.incrementY();
    }
    store();
  }
}

class PairManipulator extends Thread {
  private PairManager pm;
  private int checkCounter = 0;
  private class PairChecker extends Thread {
    PairChecker() { start(); }
    public void run() {
      while(true) {
        checkCounter++;
        pm.getPair().checkState();
      }
    }
  }
  public PairManipulator(PairManager pm) {
    this.pm = pm;
    start();
    new PairChecker();
  }
  public void run() {
    while(true) {
      pm.doTask();
    }
  }
  public String toString() {
    return "Pair: " + pm.getPair() +
      " checkCounter = " + checkCounter;
  }
}

public class CriticalSection {
  public static void main(String[] args) {
    // Test the two different approaches:
    final PairManipulator
      pm1 = new PairManipulator(new PairManager1()),
      pm2 = new PairManipulator(new PairManager2());
    new Timer(true).schedule(new TimerTask() {
      public void run() {
        System.out.println("pm1: " + pm1);
        System.out.println("pm2: " + pm2);
        System.exit(0);
      }
    }, 500); // run() after 500 milliseconds
  }
} ///:~


As noted, Pair is not thread-safe because its invariant (admittedly arbitrary) requires that both variables maintain the same values. In addition, as seen earlier in this chapter, the increment operations are not thread-safe, and because none of the methods are synchronized, you can’t trust a Pair object to stay uncorrupted in a threaded program.

The PairManager class holds a Pair object and controls all access to it. Note that the only public methods are getPair( ), which is synchronized, and the abstract doTask( ). Synchronization for this method will be handled when it is implemented.

The structure of PairManager, where some of the functionality is implemented in the base class with one or more abstract methods defined in derived classes, is called a Template Method in Design Patterns parlance.[71] Design patterns allow you to encapsulate change in your code; here, the part that is changing is the template method doTask( ). In PairManager1 the entire doTask( ) is synchronized, but in PairManager2 only part of doTask( ) is synchronized by using a synchronized block. Note that the synchronized keyword is not part of the method signature and thus may be added during overriding.

PairManager2 is observing, in effect, that store( ) is a protected method and thus is not available to the general client, but only to subclasses. Thus, it doesn’t necessarily need to be guarded inside a synchronized method, and is instead placed outside of the synchronized block.

A synchronized block must be given an object to synchronize upon, and usually the most sensible object to use is just the current object that the method is being called for: synchronized(this), which is the approach taken in PairManager2. That way, when the lock is acquired for the synchronized block, other synchronized methods in the object cannot be called. So the effect is that of simply reducing the scope of synchronization.

Sometimes this isn’t what you want, in which case you can create a separate object and synchronize on that. The following example demonstrates that two threads can enter an object when the methods in that object synchronize on different locks:

//: c13:SyncObject.java
// Synchronizing on another object
import com.bruceeckel.simpletest.*;

class DualSynch {
  private Object syncObject = new Object();
  public synchronized void f() {
    System.out.println("Inside f()");
    // Doesn't release lock:
    try {
      Thread.sleep(500);
    } catch(InterruptedException e) {
      throw new RuntimeException(e);
    }
    System.out.println("Leaving f()");
  }
  public void g() {
    synchronized(syncObject) {
      System.out.println("Inside g()");
      try {
        Thread.sleep(500);
      } catch(InterruptedException e) {
        throw new RuntimeException(e);
      }
      System.out.println("Leaving g()");
    }
  }
}

public class SyncObject {
  private static Test monitor = new Test();
  public static void main(String[] args) {
    final DualSynch ds = new DualSynch();
    new Thread() {
      public void run() {
        ds.f();
      }
    }.start();
    ds.g();
    monitor.expect(new String[] {
      "Inside g()",
      "Inside f()",
      "Leaving g()",
      "Leaving f()"
    }, Test.WAIT + Test.IGNORE_ORDER);
  }
} ///:~


The DualSync method f( ) synchronizes on this (by synchronizing the entire method) and g( ) has a synchronized block that synchronizes on syncObject. Thus, the two synchronizations are independent. This is demonstrated in main( ) by creating a Thread that calls f( ). The main( ) thread is used to call g( ). You can see from the output that both methods are running at the same time, so neither one is blocked by the synchronization of the other.

Returning to CriticalSection.java, PairManipulator is created to test the two different types of PairManager by running doTask( ) in one thread and an instance of the inner class PairChecker in the other. To trace how often it is able to run the test, PairChecker increments checkCounter every time it is successful. In main( ), two PairManipulator objects are created and allowed to run for awhile. When the Timer runs out, it executes its run( ) method, that displays the results of each PairManipulator and exits. When you run the program, you should see something like this:

pm1: Pair: x: 58892, y: 58892 checkCounter = 44974
pm2: Pair: x: 73153, y: 73153 checkCounter = 100535


Although you will probably see a lot of variation from one run to the next, in general you will see that PairManager1.doTask( ) does not allow the PairChecker nearly as much access as PairManager2.doTask( ), which has the synchronized block and thus provides more unlocked time. This is typically the reason that you want to use a synchronized block instead of synchronizing the whole method: to allow other threads more access (as long as it is safe to do so).

Of course, all synchronization depends on programmer diligence: Every piece of code that can access a shared resource must be wrapped in an appropriate synchronized block.
Thinking in Java
Prev Contents / Index Next


 
 
   Reproduced courtesy of Bruce Eckel, MindView, Inc. Design by Interspire