SHARE
Facebook X Pinterest WhatsApp

Sharing limited resources

Bruce Eckel’s Thinking in Java Contents | Prev | Next You can think of a single-threaded program as one lonely entity moving around through your problem space and doing one thing at a time. Because there’s only one entity, you never have to think about the problem of two entities trying to use the same […]

Written By
thumbnail
CodeGuru Staff
CodeGuru Staff
Mar 1, 2001
CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More

You


can think of a single-threaded program as one lonely entity moving around


through your problem space and doing one thing at a time. Because there’s


only one entity, you never have to think about the problem of two entities


trying to use the same resource at the same time, like two people trying to


park in the same space, walk through a door at the same time, or even talk at


the same time.

With


multithreading, things aren’t lonely anymore, but you now have the


possibility of two or more threads trying to use the same limited resource at


once. Colliding over a resource must be prevented or else you’ll have two


threads trying to access the same bank account at the same time, print to the


same printer, or adjust the same valve, etc.


Improperly
accessing resources

Consider


a variation on the counters that have been used so far in this chapter. In the


following example, each thread contains two counters that are incremented and


displayed inside


run( )

.


In addition, there’s another thread of class


Watcher

that is watching the counters to see if they’re always equivalent. This


seems like a needless activity, since looking at the code it appears obvious


that the counters will always be the same. But that’s where the surprise


comes in. Here’s the first version of the program:

//: Sharing1.java
// Problems with resource sharing while threading
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
 
class TwoCounter extends Thread {
  private boolean started = false;
  private TextField
    t1 = new TextField(5),
    t2 = new TextField(5);
  private Label l =
    new Label("count1 == count2");
  private int count1 = 0, count2 = 0;
  // Add the display components as a panel
  // to the given container:
  public TwoCounter(Container c) {
    Panel p = new Panel();
    p.add(t1);
    p.add(t2);
    p.add(l);
    c.add(p);
  }
  public void start() {
    if(!started) {
      started = true;
      super.start();
    }
  }
  public void run() {
    while (true) {
      t1.setText(Integer.toString(count1++));
      t2.setText(Integer.toString(count2++));
      try {
        sleep(500);
      } catch (InterruptedException e){}
    }
  }
  public void synchTest() {
    Sharing1.incrementAccess();
    if(count1 != count2)
      l.setText("Unsynched");
  }
}
 
class Watcher extends Thread {
  private Sharing1 p;
  public Watcher(Sharing1 p) {
    this.p = p;
    start();
  }
  public void run() {
    while(true) {
      for(int i = 0; i < p.s.length; i++)
        p.s[i].synchTest();
      try {
        sleep(500);
      } catch (InterruptedException e){}
    }
  }
}
 
public class Sharing1 extends Applet {
  TwoCounter[] s;
  private static int accessCount = 0;
  private static TextField aCount =
    new TextField("0", 10);
  public static void incrementAccess() {
    accessCount++;
    aCount.setText(Integer.toString(accessCount));
  }
  private Button
    start = new Button("Start"),
    observer = new Button("Observe");
  private boolean isApplet = true;
  private int numCounters = 0;
  private int numObservers = 0;
  public void init() {
    if(isApplet) {
      numCounters =
        Integer.parseInt(getParameter("size"));
      numObservers =
        Integer.parseInt(
          getParameter("observers"));
    }
    s = new TwoCounter[numCounters];
    for(int i = 0; i < s.length; i++)
      s[i] = new TwoCounter(this);
    Panel p = new Panel();
    start.addActionListener(new StartL());
    p.add(start);
    observer.addActionListener(new ObserverL());
    p.add(observer);
    p.add(new Label("Access Count"));
    p.add(aCount);
    add(p);
  }
  class StartL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      for(int i = 0; i < s.length; i++)
        s[i].start();
    }
  }
  class ObserverL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      for(int i = 0; i < numObservers; i++)
        new Watcher(Sharing1.this);
    }
  }
  public static void main(String[] args) {
    Sharing1 applet = new Sharing1();
    // This isn't an applet, so set the flag and
    // produce the parameter values from args:
    applet.isApplet = false;
    applet.numCounters =
      (args.length == 0 ? 5 :
        Integer.parseInt(args[0]));
    applet.numObservers =
      (args.length < 2 ? 5 :
        Integer.parseInt(args[1]));
    Frame aFrame = new Frame("Sharing1");
    aFrame.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e){
          System.exit(0);
        }
      });
    aFrame.add(applet, BorderLayout.CENTER);
    aFrame.setSize(350, applet.numCounters *100);
    applet.init();
    applet.start();
    aFrame.setVisible(true);
  }
} ///:~ 

As


before, each counter contains its own display components: two text fields and a


label that initially indicates that the counts are equivalent. These components


are added to the


Container

in


the


TwoCounter

constructor. Because this thread is started via a button press by the user,


it’s possible that


start( )

could be called more than once. It’s illegal for


Thread.start( )

to be called more than once for a thread (an exception is thrown). You can see


that the machinery to prevent this in the


started

flag


and the overridden


start( )

method.

In


run( )

,


count1

and


count2

are incremented and displayed in a manner that would seem to keep them


identical. Then

sleep( )
is called; without this call the program balks because it becomes hard for the
CPU to swap tasks.

The


synchTest( )

method performs the apparently useless activity of checking to see if


count1

is equivalent to


count2

;


if they are not equivalent it sets the label to “Unsynched” to


indicate this. But first, it calls a static member of the class


Sharing1

that increments and displays an access counter to show how many times this


check has occurred successfully. (The reason for this will become apparent in


future variations of this example.)

The


Watcher

class is a thread whose job is to call


synchTest( )

for all of the


TwoCounter

objects that are active. It does this by stepping through the array


that’s kept in the


Sharing1

object. You can think of the


Watcher

as constantly peeking over the shoulders of the


TwoCounter

objects.

Sharing1

contains an array of


TwoCounter

objects that it initializes in


init( )

and starts as threads when you press the “start” button. Later,


when you press the “Observe” button, one or more observers are


created and freed upon the unsuspecting


TwoCounter

threads.

Note


that to run this as an applet in a browser, your Web page will need to contain


the lines:

<applet code=Sharing1 width=650 height=500>
<param name=size value="20">
<param name=observers value="1">
</applet>

You


can change the width, height, and parameters to suit your experimental tastes.


By changing the


size

and


observers

you’ll change the behavior of the program. You can also see that this


program is set up to run as a stand-alone application by pulling the arguments


from the command line (or providing defaults).

Here’s


the surprising part. In


TwoCounter.run( )

,


the infinite loop is just repeatedly passing over the adjacent lines:

t1.setText(Integer.toString(count1++));
t2.setText(Integer.toString(count2++));

(as


well as sleeping, but that’s not important here). When you run the


program, however, you’ll discover that


count1

and


count2

will be observed (by the


Watcher

)


to be unequal at times! This is because of the nature of threads – they


can be

suspended
at any time. So at times, the suspension occurs
between
the execution of the above two lines, and the
Watcher
thread happens to come along and perform the comparison at just this moment,
thus finding the two counters to be different.

This


example shows a fundamental problem with using threads. You never know when a


thread might be run. Imagine sitting at a table with a fork, about to spear the


last piece of food on your plate and as your fork reaches for it, the food


suddenly vanishes (because your thread was suspended and another thread came in


and stole the food). That’s the problem that you’re dealing with.

Sometimes


you don’t care if a resource is being accessed at the same time


you’re trying to use it (the food is on some other plate). But for


multithreading to work, you need some way to prevent two threads from accessing


the same resource, at least during critical periods.

Preventing


this kind of collision is simply a matter of putting a lock on a resource when


one thread is using it. The first thread that accesses a resource locks it, and


then the other threads cannot access that resource until it is unlocked, at


which time another thread locks and uses it, etc. If the front seat of the car


is the limited resource, the child who shouts “Dibs!” asserts the


lock.


How
Java shares resources

Java


has built-in support to prevent collisions over one kind of resource: the


memory in an object. Since you typically make the data elements of a class

private
and access that memory only through methods, you can prevent collisions by
making a particular method
synchronized.
Only one thread at a time can call a
synchronized
method for a particular object (although that thread can call more than one of
the object’s synchronized methods). Here are simple
synchronized
methods:
synchronized void f() { /* ... */ }
synchronized void g(){ /* ... */ }

Each


object contains a single

lock
(also called a
monitor)
that is automatically part of the object (you don’t have to write any
special code). When you call any
synchronized
method, that object is locked and no other
synchronized
method of that object can be called until the first one finishes and releases
the lock. In the example above, if
f( )
is called for an object,
g( )
cannot be called for the same object until
f( )
is completed and releases the lock. Thus, there’s a single lock
that’s shared by all the
synchronized
methods of a particular object, and this lock prevents common memory from being
written by more than one method at a time (i.e. more than one thread at a time).

There’s


also a single lock per class (as part of the

Class
object for the class), so that
synchronized
static
methods can lock each other out from
static
data
on a class-wide basis.

Note


that if you want to guard some other resource from simultaneous access by


multiple threads, you can do so by forcing access to that resource through


synchronized

methods.


Synchronizing
the counters

Armed


with this new keyword it appears that the solution is at hand: we’ll


simply use the


synchronized

keyword for the methods in


TwoCounter

.


The following example is the same as the previous one, with the addition of the


new keyword:

//: Sharing2.java
// Using the synchronized keyword to prevent
// multiple access to a particular resource.
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
 
class TwoCounter2 extends Thread {
  private boolean started = false;
  private TextField
    t1 = new TextField(5),
    t2 = new TextField(5);
  private Label l =
    new Label("count1 == count2");
  private int count1 = 0, count2 = 0;
  public TwoCounter2(Container c) {
    Panel p = new Panel();
    p.add(t1);
    p.add(t2);
    p.add(l);
    c.add(p);
  }
  public void start() {
    if(!started) {
      started = true;
      super.start();
    }
  }
  public synchronized void run() {
    while (true) {
      t1.setText(Integer.toString(count1++));
      t2.setText(Integer.toString(count2++));
      try {
        sleep(500);
      } catch (InterruptedException e){}
    }
  }
  public synchronized void synchTest() {
    Sharing2.incrementAccess();
    if(count1 != count2)
      l.setText("Unsynched");
  }
}
 
class Watcher2 extends Thread {
  private Sharing2 p;
  public Watcher2(Sharing2 p) {
    this.p = p;
    start();
  }
  public void run() {
    while(true) {
      for(int i = 0; i < p.s.length; i++)
        p.s[i].synchTest();
      try {
        sleep(500);
      } catch (InterruptedException e){}
    }
  }
}
 
public class Sharing2 extends Applet {
  TwoCounter2[] s;
  private static int accessCount = 0;
  private static TextField aCount =
    new TextField("0", 10);
  public static void incrementAccess() {
    accessCount++;
    aCount.setText(Integer.toString(accessCount));
  }
  private Button
    start = new Button("Start"),
    observer = new Button("Observe");
  private boolean isApplet = true;
  private int numCounters = 0;
  private int numObservers = 0;
  public void init() {
    if(isApplet) {
      numCounters =
        Integer.parseInt(getParameter("size"));
      numObservers =
        Integer.parseInt(
          getParameter("observers"));
    }
    s = new TwoCounter2[numCounters];
    for(int i = 0; i < s.length; i++)
      s[i] = new TwoCounter2(this);
    Panel p = new Panel();
    start.addActionListener(new StartL());
    p.add(start);
    observer.addActionListener(new ObserverL());
    p.add(observer);
    p.add(new Label("Access Count"));
    p.add(aCount);
    add(p);
  }
  class StartL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      for(int i = 0; i < s.length; i++)
        s[i].start();
    }
  }
  class ObserverL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      for(int i = 0; i < numObservers; i++)
        new Watcher2(Sharing2.this);
    }
  }
  public static void main(String[] args) {
    Sharing2 applet = new Sharing2();
    // This isn't an applet, so set the flag and
    // produce the parameter values from args:
    applet.isApplet = false;
    applet.numCounters =
      (args.length == 0 ? 5 :
        Integer.parseInt(args[0]));
    applet.numObservers =
      (args.length < 2 ? 5 :
        Integer.parseInt(args[1]));
    Frame aFrame = new Frame("Sharing2");
    aFrame.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e){
          System.exit(0);
        }
      });
    aFrame.add(applet, BorderLayout.CENTER);
    aFrame.setSize(350, applet.numCounters *100);
    applet.init();
    applet.start();
    aFrame.setVisible(true);
  }
} ///:~ 

You’ll


notice that


both
run( )

and


synchTest( )

are


synchronized

.


If you synchronize only one of the methods, then the other is free to ignore


the object lock and can be called with impunity. This is an important point:


Every method that accesses a critical shared resource must be


synchronized

or it won’t work right.

Now


a new issue arises. The


Watcher2

can never get a peek at what’s going on because the entire


run( )

method has been


synchronized

,


and since


run( )

is always running for each object the lock is always tied up and


synchTest( )

can never be called. You can see this because the


accessCount

never changes.

What


we’d like for this example is a way to isolate only


part

of the code inside


run( )

.


The section of code you want to isolate this way is called a

critical
section

and you use the
synchronized
keyword in a different way to set up a critical section. Java supports critical
sections with the
synchronized
block;

this time
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, assuming all
  // threads respect syncObject's lock
}

Before


the synchronized block can be entered, the lock must be acquired on


syncObject

.


If some other thread already has this lock, then the block cannot be entered


until the lock is given up.

The


Sharing2

example can be modified by removing the


synchronized

keyword from the entire


run( )

method and instead putting a


synchronized

block around the two critical lines. But what object should be used as the


lock? The one that is already respected by


synchTest( )

,


which is the current object (


this

)!


So the modified


run( )

looks like this:

  public void run() {
    while (true) {
      synchronized(this) {
        t1.setText(Integer.toString(count1++));
        t2.setText(Integer.toString(count2++));
      }
      try {
        sleep(500);
      } catch (InterruptedException e){}
    }
  }

This


is the only change that must be made to


Sharing2.java

,


and you’ll see that while the two counters are never out of synch


(according to when the


Watcher

is allowed to look at them), there is still adequate access provided to the


Watcher

during the execution of


run( )

.

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.


Synchronized
efficiency

Since


having two methods write to the same piece of data


never

sounds


like a particularly good idea, it might seem to make sense for all methods to


be automatically


synchronized

and eliminate the


synchronized

keyword altogether. (Of course, the example with a


synchronized
run( )

shows that this wouldn’t work either.) But it turns out that acquiring a


lock is not a cheap operation – it multiplies the cost of a method call


(that is, entering and exiting from the method, not executing the body of the


method) by a minimum of four times, and could be more depending on your


implementation. So if you know that a particular method will not cause


contention problems it is expedient to leave off the


synchronized

keyword.


Java
Beans revisited

Now


that you understand synchronization you can take another look at

Java
Beans. Whenever you create a Bean, you must assume that it will run in a
multithreaded environment. This means that:
  1. Whenever
    possible, all the public methods of a Bean should be
    synchronized.
    Of course, this incurs the
    synchronized
    runtime overhead. If that’s a problem, methods that will not cause
    problems in critical sections can be left un-
    synchronized,
    but keep in mind that this is not always obvious. Methods that qualify tend to
    be small (such as
    getCircleSize( )
    in the following example) and/or “atomic,” that is, the method call
    executes in such a short amount of code that the object cannot be changed
    during execution. Making such methods un-
    synchronized
    might
    not have a significant effect on the execution speed of your program. You might
    as well make all
    public
    methods of a Bean
    synchronized
    and remove the
    synchronized
    keyword only when you know for sure that it’s necessary and that it makes
    a difference.
  2. When
    firing a multicast
    event to a bunch of listeners interested in that event, you must assume that
    listeners might be added or removed while moving through the list.

The


first point is fairly easy to deal with, but the second point requires a little


more thought. Consider the


BangBean.java

example presented in the last chapter. That ducked out of the multithreading


question by ignoring the


synchronized

keyword (which hadn’t been introduced yet) and making the event unicast.


Here’s that example modified to work in a multithreaded environment and


to use multicasting for events:

//: BangBean2.java
// You should write your Beans this way so they 
// can run in a multithreaded environment.
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.io.*;
 
public class BangBean2 extends Canvas
    implements Serializable {
  private int xm, ym;
  private int cSize = 20; // Circle size
  private String text = "Bang!";
  private int fontSize = 48;
  private Color tColor = Color.red;
  private Vector actionListeners = new Vector();
  public BangBean2() {
    addMouseListener(new ML());
    addMouseMotionListener(new MM());
  }
  public synchronized int getCircleSize() {
    return cSize;
  }
  public synchronized void
  setCircleSize(int newSize) {
    cSize = newSize;
  }
  public synchronized String getBangText() {
    return text;
  }
  public synchronized void
  setBangText(String newText) {
    text = newText;
  }
  public synchronized int getFontSize() {
    return fontSize;
  }
  public synchronized void
  setFontSize(int newSize) {
    fontSize = newSize;
  }
  public synchronized Color getTextColor() {
    return tColor;
  }
  public synchronized void
  setTextColor(Color newColor) {
    tColor = newColor;
  }
  public void paint(Graphics g) {
    g.setColor(Color.black);
    g.drawOval(xm - cSize/2, ym - cSize/2,
      cSize, cSize);
  }
  // This is a multicast listener, which is
  // more typically used than the unicast
  // approach taken in BangBean.java:
  public synchronized void addActionListener (
      ActionListener l) {
    actionListeners.addElement(l);
  }
  public synchronized void removeActionListener(
      ActionListener l) {
    actionListeners.removeElement(l);
  }
  // Notice this isn't synchronized:
  public void notifyListeners() {
    ActionEvent a =
      new ActionEvent(BangBean2.this,
        ActionEvent.ACTION_PERFORMED, null);
    Vector lv = null;
    // Make a copy of the vector in case someone
    // adds a listener while we're 
    // calling listeners:
    synchronized(this) {
      lv = (Vector)actionListeners.clone();
    }
    // Call all the listener methods:
    for(int i = 0; i < lv.size(); i++) {
      ActionListener al =
        (ActionListener)lv.elementAt(i);
      al.actionPerformed(a);
    }
  }
  class ML extends MouseAdapter {
    public void mousePressed(MouseEvent e) {
      Graphics g = getGraphics();
      g.setColor(tColor);
      g.setFont(
        new Font(
          "TimesRoman", Font.BOLD, fontSize));
      int width =
        g.getFontMetrics().stringWidth(text);
      g.drawString(text,
        (getSize().width - width) /2,
        getSize().height/2);
      g.dispose();
      notifyListeners();
    }
  }
  class MM extends MouseMotionAdapter {
    public void mouseMoved(MouseEvent e) {
      xm = e.getX();
      ym = e.getY();
      repaint();
    }
  }
  // Testing the BangBean2:
  public static void main(String[] args) {
    BangBean2 bb = new BangBean2();
    bb.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e){
        System.out.println("ActionEvent" + e);
      }
    });
    bb.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e){
        System.out.println("BangBean2 action");
      }
    });
    bb.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e){
        System.out.println("More action");
      }
    });
    Frame aFrame = new Frame("BangBean2 Test");
    aFrame.addWindowListener(new WindowAdapter(){
      public void windowClosing(WindowEvent e) {
        System.exit(0);
      }
    });
    aFrame.add(bb, BorderLayout.CENTER);
    aFrame.setSize(300,300);
    aFrame.setVisible(true);
  }
} ///:~ 

Adding


synchronized

to the methods is an easy change. However, notice in

addActionListener( )
and
removeActionListener( )
that the
ActionListeners
are now added to and removed from a
Vector,
so you can have as many as you want.

You


can see that the method

notifyListeners( )
is
not
synchronized.
It can be called from more than one thread at a time. It’s also possible
for
addActionListener( )
or
removeActionListener( )
to be called in the middle of a call to
notifyListeners( ),
which is a problem since it traverses the
Vector
actionListeners
.
To alleviate the problem, the
Vector
is cloned inside a
synchronized
clause and the clone is traversed. This way the original
Vector
can be manipulated without impact on
notifyListeners( ).

The


paint( )

method is also not

synchronized.
Deciding whether to synchronize overridden methods is not as clear as when
you’re just adding your own methods. In this example it turns out that
paint( )
seems to work OK whether it’s
synchronized
or not. But the issues you must consider are:
  1. Does
    the method modify the state of “critical” variables within the
    object? To discover whether the variables are “critical” you must
    determine whether they will be read or set by other threads in the program. (In
    this case, the reading or setting is virtually always accomplished via
    synchronized
    methods, so you can just examine those.) In the case of
    paint( ),
    no modification takes place.
  2. Does
    the method depend on the state of these “critical” variables? If a
    synchronized
    method modifies a variable that your method uses, then you might very well want
    to make your method
    synchronized
    as well. Based on this, you might observe that
    cSize
    is changed by
    synchronized
    methods and therefore
    paint( )
    should be
    synchronized.
    Here, however, you can ask “What’s the worst thing that will happen
    if
    cSize
    is changed during a
    paint( )?”
    When you see that it’s nothing too bad, and a transient effect at that,
    it’s best to leave
    paint( )
    un-
    synchronized
    to prevent the extra overhead from the
    synchronized
    method call.
  3. A
    third clue is to notice whether the base-class version of
    paint( )
    is
    synchronized,
    which it isn’t. This isn’t an airtight argument, just a clue. In
    this case, for example, a field that
    is
    changed via
    synchronized
    methods (that is
    cSize)
    has been mixed into the
    paint( )
    formula and might have changed the situation. Notice, however, that synchronized
    doesn’t inherit – that is, if a method is
    synchronized
    in the base class then it
    is
    not

    automatically
    synchronized
    in the derived class overridden version.

The


test code in


TestBangBean2

has been modified from that in the previous chapter to demonstrate the


multicast ability of


BangBean2

by adding extra listeners.


Contents

|

Prev

|

Next

Recommended for you...

Top Five Most Popular Front-end Frameworks
Tapas Pal
Mar 5, 2018
DocFlex/Javadoc: Multi-Format Doclet & Rapid Doclet Development Tool
CodeGuru Staff
Apr 23, 2012
Top Down with Memorization Complexity
CodeGuru Staff
Feb 24, 2012
Building and Using the Secret Service Java API
CodeGuru Staff
Feb 21, 2012
CodeGuru Logo

CodeGuru covers topics related to Microsoft-related software development, mobile development, database management, and web application programming. In addition to tutorials and how-tos that teach programmers how to code in Microsoft-related languages and frameworks like C# and .Net, we also publish articles on software development tools, the latest in developer news, and advice for project managers. Cloud services such as Microsoft Azure and database options including SQL Server and MSSQL are also frequently covered.

Property of TechnologyAdvice. © 2025 TechnologyAdvice. All Rights Reserved

Advertiser Disclosure: Some of the products that appear on this site are from companies from which TechnologyAdvice receives compensation. This compensation may impact how and where products appear on this site including, for example, the order in which they appear. TechnologyAdvice does not include all companies or all types of products available in the marketplace.