|
|
|
|
JavaBeans and synchronization
Whenever you create a Bean, you must assume that it will run in a multithreaded environment. This means that:
- Whenever possible, all the
public methods of a Bean should be synchronized. Of course, this
incurs the synchronized run-time overhead (which has been significantly
reduced in recent versions of the JDK). 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.
- 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.
//: c14:BangBean2.java
// You should write your Beans this way so they
// can run in a multithreaded environment.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.io.*;
import com.bruceeckel.swing.*;
public class BangBean2 extends JPanel
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 ArrayList actionListeners = new ArrayList();
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 paintComponent(Graphics g) {
super.paintComponent(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.add(l);
}
public synchronized void
removeActionListener(ActionListener l) {
actionListeners.remove(l);
}
// Notice this isn't synchronized:
public void notifyListeners() {
ActionEvent a = new ActionEvent(BangBean2.this,
ActionEvent.ACTION_PERFORMED, null);
ArrayList lv = null;
// Make a shallow copy of the List in case
// someone adds a listener while we're
// calling listeners:
synchronized(this) {
lv = (ArrayList)actionListeners.clone();
}
// Call all the listener methods:
for(int i = 0; i < lv.size(); i++)
((ActionListener)lv.get(i)).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();
}
}
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");
}
});
Console.run(bb, 300, 300);
}
} ///:~
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 an ArrayList, 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 because it traverses the ArrayList actionListeners. To alleviate the problem, the ArrayList is cloned inside a synchronized clause, and the clone is traversed (see Appendix A for details of cloning). This way, the original ArrayList can be manipulated without impact on notifyListeners( ).
The paintComponent( ) 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 paintComponent( ) seems to work OK whether it’s synchronized or not. But the issues you must consider are:
- 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 paintComponent( ), no
modification takes place.
- 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 paintComponent( ) should
be synchronized. Here, however, you can ask “What’s the worst
thing that will happen if cSize is changed during a
paintComponent( )?” When you see that it’s nothing too
bad, and a transient effect at that, you can decide to leave
paintComponent( ) un-synchronized to prevent the extra
overhead from the synchronized method call.
- A third clue is to notice whether the base-class version of
paintComponent( ) 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 paintComponent( ) 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.
- paint( ) and paintComponent( ) are methods that must
be as fast as possible. Anything that takes processing overhead out of these
methods is highly recommended, so if you think you need to synchronize these
methods it may be an indicator of bad
design.
The test code in main( ) has been modified from that seen in BangBeanTest to demonstrate the multicast ability of BangBean2 by adding extra listeners.
|
|
|