Runnable revisited

Bruce Eckel's Thinking in Java Contents | Prev | Next

//: ColorBoxes.java
// Using the Runnable interface
import java.awt.*;
import java.awt.event.*;
 
class CBox extends Canvas implements Runnable {
  private Thread t;
  private int pause;
  private static final Color[] colors = { 
    Color.black, Color.blue, Color.cyan, 
    Color.darkGray, Color.gray, Color.green,
    Color.lightGray, Color.magenta, 
    Color.orange, Color.pink, Color.red, 
    Color.white, Color.yellow 
  };
  private Color cColor = newColor();
  private static final Color newColor() {
    return colors[
      (int)(Math.random() * colors.length)
    ];
  }
  public void paint(Graphics  g) {
    g.setColor(cColor);
    Dimension s = getSize();
    g.fillRect(0, 0, s.width, s.height);
  }
  public CBox(int pause) {
    this.pause = pause;
    t = new Thread(this);
    t.start(); 
  }
  public void run() {
    while(true) {
      cColor = newColor();
      repaint();
      try {
        t.sleep(pause);
      } catch(InterruptedException e) {}
    } 
  }
} 
 
public class ColorBoxes extends Frame {
  public ColorBoxes(int pause, int grid) {
    setTitle("ColorBoxes");
    setLayout(new GridLayout(grid, grid));
    for (int i = 0; i < grid * grid; i++)
      add(new CBox(pause));
    addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
        System.exit(0);
      }
    });
  }   
  public static void main(String[] args) {
    int pause = 50;
    int grid = 8;
    if(args.length > 0) 
      pause = Integer.parseInt(args[0]);
    if(args.length > 1)
      grid = Integer.parseInt(args[1]);
    Frame f = new ColorBoxes(pause, grid);
    f.setSize(500, 400);
    f.setVisible(true);  
  }
} ///:~ 

Too many threads

At some point, you’ll find that ColorBoxes bogs down. On my machine, this occurred somewhere after a 10 x 10 grid. Why does this happen? You’re naturally suspicious that the AWT might have something to do with it, so here’s an example that tests that premise by making fewer threads. The code is reorganized so that a Vector implements Runnable and that Vector holds a number of color blocks and randomly chooses ones to update. Then a number of these Vector objects are created, depending roughly on the grid dimension you choose. As a result, you have far fewer threads than color blocks, so if there’s a speedup we’ll know it was because there were too many threads in the previous example:

//: ColorBoxes2.java
// Balancing thread use
import java.awt.*;
import java.awt.event.*;
import java.util.*;
 
class CBox2 extends Canvas {
  private static final Color[] colors = { 
    Color.black, Color.blue, Color.cyan, 
    Color.darkGray, Color.gray, Color.green,
    Color.lightGray, Color.magenta, 
    Color.orange, Color.pink, Color.red, 
    Color.white, Color.yellow 
  };
  private Color cColor = newColor();
  private static final Color newColor() {
    return colors[
      (int)(Math.random() * colors.length)
    ];
  }
  void nextColor() {
    cColor = newColor();
    repaint();
  }
  public void paint(Graphics  g) {
    g.setColor(cColor);
    Dimension s = getSize();
    g.fillRect(0, 0, s.width, s.height);
  }
}
 
class CBoxVector 
  extends Vector implements Runnable {
  private Thread t;
  private int pause;
  public CBoxVector(int pause) {
    this.pause = pause;
    t = new Thread(this);
  }
  public void go() { t.start(); }
  public void run() {
    while(true) {
      int i = (int)(Math.random() * size());
      ((CBox2)elementAt(i)).nextColor();
      try {
        t.sleep(pause);
      } catch(InterruptedException e) {}
    } 
  }
}
 
public class ColorBoxes2 extends Frame {
  private CBoxVector[] v;
  public ColorBoxes2(int pause, int grid) {
    setTitle("ColorBoxes2");
    setLayout(new GridLayout(grid, grid));
    v = new CBoxVector[grid];
    for(int i = 0; i < grid; i++)
      v[i] = new CBoxVector(pause);
    for (int i = 0; i < grid * grid; i++) {
      v[i % grid].addElement(new CBox2());
      add((CBox2)v[i % grid].lastElement());
    }
    for(int i = 0; i < grid; i++)
      v[i].go();
    addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
        System.exit(0);
      }
    });
  }   
  public static void main(String[] args) {
    // Shorter default pause than ColorBoxes:
    int pause = 5;
    int grid = 8;
    if(args.length > 0) 
      pause = Integer.parseInt(args[0]);
    if(args.length > 1)
      grid = Integer.parseInt(args[1]);
    Frame f = new ColorBoxes2(pause, grid);
    f.setSize(500, 400);
    f.setVisible(true);  
  }
} ///:~ 

In ColorBoxes2 an array of CBoxVector is created and initialized to hold grid CBoxVectors, each of which knows how long to sleep. An equal number of Cbox2 objects is then added to each CBoxVector, and each vector is told to go( ), which starts its thread.

CBox2 is similar to CBox: it paints itself with a randomly-chosen color. But that’s all a CBox2 does. All of the threading has been moved into CBoxVector.

The CBoxVector could also have inherited Thread and had a member object of type Vector. That design has the advantage that the addElement( ) and elementAt( ) methods could then be given specific argument and return value types instead of generic Objects. (Their names could also be changed to something shorter.) However, the design used here seemed at first glance to require less code. In addition, it automatically retains all the other behaviors of a Vector. With all the casting and parentheses necessary for elementAt( ), this might not be the case as your body of code grows.

As before, when you implement Runnable you don’t get all of the equipment that comes with Thread, so you have to create a new Thread and hand yourself to its constructor in order to have something to start( ), as you can see in the CBoxVector constructor and in go( ). The run( ) method simply chooses a random element number within the vector and calls nextColor( ) for that element to cause it to choose a new randomly-selected color.

Upon running this program, you see that it does indeed run faster and respond more quickly (for instance, when you interrupt it, it stops more quickly), and it doesn’t seem to bog down as much at higher grid sizes. Thus, a new factor is added into the threading equation: you must watch to see that you don’t have “too many threads” (whatever that turns out to mean for your particular program and platform). If you do, you must try to use techniques like the one above to “balance” the number of threads in your program. If you see performance problems in a multithreaded program you now have a number of issues to examine:

  1. Do you have enough calls to sleep( ), yield( ), and/or wait( )?
  2. Are calls to sleep( ) long enough?
  3. Are you running too many threads?
  4. Have you tried different platforms and JVMs?
Issues like this are one reason that multithreaded programming is often considered an art.



Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Organizations are increasingly gravitating toward mobile-first application development as they assess the need to revamp their application portfolios to support touch computing and mobility. Consumerization has brought higher expectations for application usability along with the mobile devices themselves. Enterprises are increasingly shifting their new application acquisitions and development efforts toward mobile platforms. With this backdrop, it is natural to expect application platform vendors to invest in …

  • The mobile revolution and the need for mobile apps have created an unprecedented set of challenges for IT organizations. This eBook discuss these challenges and how organizations can address them by making their mobile app development processes more efficient and more effective.

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date