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

JavaBeans and synchronization

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 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.
  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.
    //: 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:

    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 paintComponent( ), 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 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.
    3. 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.
    4. 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.
    Thinking in Java
    Prev Contents / Index Next


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