Principle 14.5.8. Single-Threaded Loop.
In a single-threaded design, a loop that requires lots of iterations will completely dominate the CPU during its execution, which forces other tasks, including user I/O tasks, to wait.
JPanel
that contains the two JButton
s. The dots are drawn on a JPanel
, which is positioned in the center of a BorderLayout
design.Dotty
class (Figure 14.5.3).draw()
and clear()
methods for drawing on the GUI’s drawing panel (Figure 14.5.4).![]() |
![]() Dotty class. |
RandomDotGUI
Class
RandomDotGUI
class.import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
public class RandomDotGUI extends JFrame implements ActionListener {
private Dotty dotty; // The drawing class
private JPanel controls = new JPanel();
private JPanel canvas = new JPanel();
private JButton draw = new JButton("Draw");
private JButton clear = new JButton("Clear");
public RandomDotGUI() {
getContentPane().setLayout(new BorderLayout());
draw.addActionListener(this);
clear.addActionListener(this);
controls.add(draw);
controls.add(clear);
canvas.setBorder(BorderFactory.createTitledBorder("Drawing Canvas"));
getContentPane().add("North", controls);
getContentPane().add("Center", canvas);
getContentPane().setSize(400, 400);
}
public void actionPerformed(ActionEvent e) {
if (e.getSource() == draw) {
dotty = new Dotty(canvas);
dotty.draw();
} else {
dotty.clear();
}
} // actionPerformed()
public static void main(String args[]) {
RandomDotGUI gui = new RandomDotGUI();
gui.setSize(500, 500);
gui.setVisible(true);
}
} // RandomDotGUI
BorderLayout
and listens for action events on its JButton
s. When the user clicks the Draw button, the GUI’s actionPerformed()
method will create a new Dotty
instance and call its draw()
method:dotty = new Dotty(canvas);
dotty.draw();
Dotty
is passed a reference to the drawing canvas
. When the user clicks the Clear button, the GUI should call the dotty.clear()
method. Of course, the important question is, how responsive will the GUI be to the user’s action?Dotty
Class
Dotty
class will be to draw the dots and to report how many red dots were drawn before the canvas was cleared. Because it will be passed a reference to the drawing panel and the number of dots to draw, the Dotty
class will need instance variables to store these two values. It will also need a variable to keep track of how many dots were drawn. Finally, since it will be drawing within a fixed rectangle on the panel, the reference coordinates and dimensions of the drawing area are declared as class constants.Dotty()
constructor method will be passed a reference to a drawing panel as well as the number of dots to be drawn and will merely assign these parameters to its instance variables. In addition to its constructor method, the Dotty
class will have public draw()
and clear()
methods, which will be called from the GUI. The draw()
method will use a loop to draw random dots. The clear()
will clear the canvas and report the number of dots drawn.Dotty
class, single-threaded version.class Dotty {
// Coordinates
private static final int HREF = 20, VREF = 20, LEN = 200;
private JPanel canvas;
private int nDots; // Number of dots drawn
private int firstRed = 0; // Number of the first red dot
public Dotty(JPanel canv) {
canvas = canv;
}
public void draw() {
int width = 5; // Dot size
int height = 5;
nDots = 0;
Graphics g = canvas.getGraphics();
int k = 0;
long bound = 1000000000; // # of iterations
while (k < bound) {
int x = HREF + (int) (Math.random() * LEN);
int y = VREF + (int) (Math.random() * LEN);
if (k % 1000000 == 0) { // Every millionth iteration
g.fillOval(x, y, width, height); // Draw a dot
nDots++;
if ((Math.random() < 0.05) && (firstRed == 0)) {
g.setColor(Color.red); // Change color to red
firstRed = nDots;
}
}
k++;
} // while
} // draw()
public void clear() { // Clear screen and report result
Graphics g = canvas.getGraphics();
g.setColor(canvas.getBackground());
g.fillRect(HREF, VREF, LEN + 3, LEN + 3);
System.out.println(
"Number of dots drawn since first red = " + (nDots - firstRed));
} // clear()
} // Dotty
Dotty
is shown in Listing 14.5.6. Note how its draw()
method is designed. The drawing loop is bounded by the number of iterations. To slow down the drawing, it will iterate 1 billion times, drawing a dot on each millionth iteration. For each dot, the draw()
method picks a random location within the rectangle defined by the coordinates (HREF,VREF) and (HREF+LEN, VREF+LEN), and draws a dot there. Each time it draws a dot it computes a random number, which if less than 0.05, the drawing color is changed to red and a record is made of the number of dots drawn up to that point.draw()
method is executing, the program will be unable to respond to the GUI’s Clear button. In a single-threaded design, both the GUI and dotty
are combined into a single thread of execution (Fig. Figure 14.5.7).actionPerformed()
method is invoked. It then invokes Dotty
’s draw()
method, which must run to completion before anything else can be done. If the user clicks the Clear button while the dots are being drawn, the GUI won’t be able to get to this until all the dots are drawn.Dotty
Thread
Dotty
into a thread is to have it implement the Runnable
interface:public class Dotty implements Runnable {
// Everything else remains the same
public void run() {
draw();
}
}
Dotty
will perform the same task as before except that it will now run as a separate thread of execution. Note that its run()
method just calls the draw()
method that we defined in the previous version. When the Dotty
thread is started by the RandomDotGUI
, we will have a multithreaded program.Dotty
sleep for a short instance after it draws each dot. When a thread sleeps, any other threads that are waiting their turn will get a chance to run. If the GUI thread is waiting to handle the user’s click on Clear, it will now be able to call Dotty
’s clear()
method.draw()
is shown in Figure 14.5.11. In this version of draw()
, the thread sleeps for 1 millisecond on each iteration of the loop. This will make it possible for the GUI to run on every iteration, so it will handle user actions immediately.import java.awt.*;
import javax.swing.*; // Import Swing classes
public class Dotty implements Runnable {
// Coordinates
private static final int HREF = 20, VREF = 20, LEN = 200;
private JPanel canvas;
private int nDots; // Number of dots to draw
private int nDrawn; // Number of dots drawn
private int firstRed = 0; // Number of the first red dot
private boolean isCleared = false; // Panel is cleared
public void run() {
draw();
}
public Dotty(JPanel canv, int dots) {
canvas = canv;
nDots = dots;
}
public void draw() {
Graphics g = canvas.getGraphics();
for (nDrawn = 0; !isCleared && nDrawn < nDots; nDrawn++) {
int x = HREF + (int)(Math.random() * LEN);
int y = VREF + (int)(Math.random() * LEN);
g.fillOval(x, y, 3, 3); // Draw a dot
if (Math.random() < 0.001 && firstRed == 0) {
g.setColor(Color.red); // Change color to red
firstRed = nDrawn;
}
try {
Thread.sleep(1); // Sleep for an instant
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
} //for
} // draw()
public void clear() {
isCleared = true;
Graphics g = canvas.getGraphics();
g.setColor( canvas.getBackground() );
g.fillRect(HREF,VREF,LEN+3,LEN+3);
System.out.println("Number of dots drawn since first red = "
+ (nDrawn-firstRed));
} // clear()
} // Dotty
Runnable
interface, this version of Dotty
can run as a separate thread.clear()
method is called, the Dotty
thread should stop running (drawing). The correct way to stop a thread is to use some variable whose value will cause the run loop (or in this case the drawing loop) to exit, so the new version of Dotty
uses the boolean
variable isCleared
to control when drawing is stopped. Note that the variable is initialized to false
and then set to true
in the clear()
method. The while loop in draw()
will exit when isCleared
becomes true
. This causes the draw()
method to return, which causes the run()
method to return, which causes the thread to stop in an orderly fashion.Runnable
interface, (2) override its run()
method, and (3) incorporate some mechanism, such as a sleep()
state, into the thread’s run algorithm so that the GUI thread will have a chance to run periodically.RandomDotGUI
RandomDotGUI
to get it to work with the new version of Dotty
. The primary change comes in the actionPerformed()
method. Each time the Draw button was clicked in the original version of this method, we created a dotty
instance and then called its draw()
method. In the revised version we must create a new Thread
and pass it an instance of Dotty
, which will then run as a separate thread:public void actionPerformed(ActionEvent e) {
if (e.getSource() == draw) {
dotty = new Dotty(canvas, NDOTS);
dottyThread = new Thread(dotty);
dottyThread.start();
} else {
dotty.clear();
}
} // actionPerformed()
dotty
we also have a reference to a Thread
named dottyThread
. This additional variable must be declared within the GUI.start()
method, it automatically calls the thread’s run()
method. When dottyThread
starts to run, it will immediately call the draw()
method and start drawing dots. After each dot is drawn, dottyThread
will sleep for an instant.Dotty.clear()
will set the isCleared
variable, which will cause the drawing loop to terminate. Once again, this is the proper way to stop a thread. Thus, as soon as the user clicks the Clear button, the Dotty
thread will stop drawing and report its result.boolean
control variable whose value can be set to true or false to exit the run()
loop.Dotty
, we have turned a single-threaded program into a multithreaded program. One thread, the GUI, handles the user interface. The second thread handles the drawing task. By forcing the drawing to sleep on each iteration, we guarantee that the GUI thread will remain responsive to the user’s actions. Figure 14.5.15 illustrates the difference between the single- and multithreaded designs. Note that the GUI thread starts and stops the drawing thread, and the GUI thread executes dotty.clear()
. The drawing thread simply executes its draw()
method. In the single-threaded version, all of these actions are done by one thread.dottyThread.draw()
will sleep for an instant on each iteration. However, the extra time is hardly noticeable. By breaking the program into two separate threads of control, one to handle the drawing task and one to handle the user interface, the result is a much more responsive program.Dotty
sleep on each iteration, what if we set its priority to Thread.MIN_PRIORITY
. What effect would you expect this to have on the program’s responsiveness compared to the single-threaded version?