Skip to main content
Engineering LibreTexts

14.4: Using Threads to Improve Interface Responsiveness

  • Page ID
    15150
  • \( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \) \( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)\(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\) \(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\)\(\newcommand{\AA}{\unicode[.8,0]{x212B}}\)

    One good use for a multithreaded program is to help make a more responsive user interface. In a single-threaded program, a program that is executing statements in a long (perhaps even infinite) loop remains unresponsive to the user’s actions until the loop is exited. Thus, the user will experience a noticeable and sometimes frustrating delay between the time an action is initiated and the time it is actually handled by the program.

    Single-Threaded Design

    It’s always a good idea that the interface be responsive to user input, but sometimes it is crucial to an application. For example, suppose a psychology experiment is trying to measure how quickly a user responds to a certain stimulus presented by a program. Obviously, for this kind of application, the program should take action as soon as the user clicks a button to indicate a response to the stimulus. Let’s work through an appropriate program design for the experiment. First, we will formally state the situation and describe what the program should do. Then, we will examine the components that would make up an effective program.

    Problem Statement

    A psychologist is conducting a psychometric experiment to measure user response to a visual cue and asks you to create the following program. The program should have two buttons. When the Draw button is clicked, the program begins drawing thousands of black dots at random locations within a rectangular region of the screen (Fig. 14.7). After a random time interval, the program begins drawing red dots. This change corresponds to the presentation of the stimulus. As soon as the stimulus is presented the user is supposed to click on a Clear button, which clears the drawing area. To provide a measure of the user’s reaction time, the program should report how many red dots were drawn before the user clicked the Clear button.

    Figure [fig-dotsgui] shows a design for this program’s GUI. It contains a control JPanel that contains the two JButtons. The dots are drawn on a JPanel, which is positioned in the center of a BorderLayout design.

    Problem Decomposition

    This program should be decomposed into two classes, a GUI to handle the user interface and a drawing class to manage the drawing. The main features of its classes are as follows:

    RandomDotGUI Class: This class manages the user interface, responding to user actions by calling methods of the Dotty class (Fig. 14.9).

    Dotty Class: This class contains draw() and clear() methods for drawing on the GUI’s drawing panel (Fig. 14.10).

    The RandomDotGUI Class

    The implementation of RandomDotGUI is shown in Figure [fig-randomdotgui]. The GUI arranges the control and drawing panels in a BorderLayout and listens for action events on its JButtons. 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, NDOTS);
    dotty.draw();

    Note that Dotty is passed a reference to the drawing canvas as well as the number of dots to be drawn. 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?

    import java.awt.*;
    import javax.swing.*;
    import java.awt.event.*;
    
    public class RandomDotGUI extends JFrame 
                                implements ActionListener  {
      public final int NDOTS = 10000;
      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, NDOTS);
          dotty.draw();
        } else {
          dotty.clear();
        }
      } // actionPerformed()
      public static void main(String args[]){
        RandomDotGUI gui = new RandomDotGUI();
        gui.setSize(400,400);
        gui.setVisible(true);        
      }
    } // RandomDotGUI

    The Dotty Class

    The purpose of the 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.

    The 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.

    import java.awt.*;
    import javax.swing.*;   // Import Swing classes
    
    public class Dotty {
                                // 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
    
      public Dotty(JPanel canv, int dots) {
        canvas = canv;
        nDots = dots;
      }
      public void draw() {
        Graphics g = canvas.getGraphics();
        for (nDrawn = 0; 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;
          }
        } //for
      } // 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 = " + (nDrawn-firstRed));
      } // clear()
    } // Dotty

    The complete implementation of Dotty is shown in Figure [fig-singledotty]. Note how its draw() method is designed. The drawing loop is bounded by the number of dots to be drawn. On each iteration, 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. On each iteration it also generates a random number. If the random number is less than 0.001, it changes the drawing color to red and keeps track of the number of dots drawn up to that point.

    The problem with this design is that as long as the 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. [fig-dottytrace]).

    When the user clicks the Draw button, the GUI’s 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.

    If you run this program with nDots set to 10,000, the program will not clear the drawing panel until all 10,000 dots are drawn, no matter when the Clear button is pressed. Therefore, the values reported for the user’s reaction time will be wrong. Obviously, since it is so unresponsive to user input, this design completely fails to satisfy the program’s specifications.

    Suppose the Java Virtual Machine (JVM) was single threaded and your program got stuck in an infinite loop. Would you be able to break out of the loop by typing some special command (such as Control-C) from the keyboard?

    Multithreaded Drawing: The Dotty Thread

    One way to remedy this problem is to create a second thread (in addition to the GUI itself) to do the drawing. The drawing thread will be responsible just for drawing, while the GUI thread will be responsible for handling user actions in the interface. The trick to making the user interface more responsive will be to interrupt the drawing thread periodically so that the GUI thread has a chance to handle any events that have occurred.

    As Figure 14.14 illustrates, the easiest way to convert 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();
        }
    }

    This version of 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.

    However, just because this program has two threads doesn’t necessarily mean that it will be any more responsive to the user. There’s no guarantee that the drawing thread will stop as soon as the Clear button is clicked. On most systems, if both threads have equal priority, the GUI thread won’t run until the drawing thread finishes drawing all N dots.

    Therefore, we have to modify our design in order to guarantee that the GUI thread will get a chance to handle the user’s actions. One good way to do this is to have 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.

    The new version of draw() is shown in Figure [fig-threadeddotty]. 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.

    -5pc

    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

    Another necessary change is that once the 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 for 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.

    Modifications to RandomDotGUI

    We don’t need to make many changes in 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()

    Note that in addition to a reference to dotty we also have a reference to a Thread named dottyThread. This additional variable must be declared within the GUI.

    Remember that when you call the 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.

    Notice how the GUI stops the drawing thread. In the new version, 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.

    Advantages of Multithreaded Design

    By creating a separate thread for 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 [fig-threadedtrace] 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.

    The trade-off involved in this design is that it will take longer to draw N random dots, since 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.

    Someone might argue that because the Java Virtual Machine uses a round-robin scheduling algorithm, it’s redundant to use the sleep() method, since the GUI thread will get its chance to run. What’s wrong with this argument for interface responsiveness?

    Instead of sleeping on each iteration, another way to make the interface more responsive would be to set the threaded Dotty’s priority to a low number, such as 1. Make this change, and experiment with its effect on the program’s responsiveness. Is it more or less responsive than sleeping on each iteration? Why?


    This page titled 14.4: Using Threads to Improve Interface Responsiveness is shared under a CC BY 4.0 license and was authored, remixed, and/or curated by Ralph Morelli & Ralph Wade via source content that was edited to the style and standards of the LibreTexts platform; a detailed edit history is available upon request.