Responsive user interfaces

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

As
a starting point, consider a program that performs some CPU-intensive operation
and thus ends up ignoring user input and being unresponsive. This one, a
combined applet/application, will simply display the result of a running counter:

//: Counter1.java
// A non-responsive user interface
package c14;
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
 
public class Counter1 extends Applet {
  private int count = 0;
  private Button
    onOff = new Button("Toggle"),
    start = new Button("Start");
  private TextField t = new TextField(10);
  private boolean runFlag = true;
  public void init() {
    add(t);
    start.addActionListener(new StartL());
    add(start);
    onOff.addActionListener(new OnOffL());
    add(onOff);
  }
  public void go() {
    while (true) {
      try {
        Thread.currentThread().sleep(100);
      } catch (InterruptedException e){}
      if(runFlag)
        t.setText(Integer.toString(count++));
    }
  }
  class StartL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      go();
    }
  }
  class OnOffL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      runFlag = !runFlag;
    }
  }
  public static void main(String[] args) {
    Counter1 applet = new Counter1();
    Frame aFrame = new Frame("Counter1");
    aFrame.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          System.exit(0);
        }
      });
    aFrame.add(applet, BorderLayout.CENTER);
    aFrame.setSize(300,200);
    applet.init();
    applet.start();
    aFrame.setVisible(true);
  }
} ///:~ 

At
this point, the AWT and applet code should be reasonably familiar from Chapter
13. The
go( )
method is where the program stays busy: it puts the current value of
count
into the
TextField
t
,
then increments
count.

When
the
start
button is pressed,
go( )
is invoked. And upon examining
go( ),
you might naively think (as I did) that it should allow multithreading because
it goes to sleep. That is, while the method is asleep, it seems like the CPU
could be busy monitoring other button presses. But it turns out that the real
problem is that
go( )
never returns, since it’s in an infinite loop, and this means that
actionPerformed( )
never returns. Since you’re stuck inside
actionPerformed( )
for the first keypress, the program can’t handle any other events. (To
get out, you must somehow kill the process; the easiest way to do this is to
press Control-C in the console window.)

The
basic problem here is that
go( )
needs to continue performing its operations, and at the same time it needs to
return so
actionPerformed( )
can complete and the user interface can continue responding to the user. But in
a conventional method like
go( )
it cannot continue
and
at the same time return control to the rest of the program. This sounds like an
impossible thing to accomplish, as if the CPU must be in two places at once,
but this is precisely the illusion that threading provides. The thread model
(and programming support in Java) is a programming convenience to simplify
juggling several operations at the same time within a single program. With
threads, the CPU will pop around and give each thread some of its time. Each
thread has the consciousness of constantly having the CPU to itself, but the
CPU’s time is actually sliced between all the threads.

Inheriting
from Thread

The
simplest way to create a thread is to inherit from class
Thread,
which has all the wiring necessary to create and run threads. The most
important method for
Thread
is
run( ),
which you must override to make the thread do your bidding. Thus,
run( )
is the code that will be executed “simultaneously” with the other
threads in a program.

//: SimpleThread.java
// Very simple Threading example
 
public class SimpleThread extends Thread {
  private int countDown = 5;
  private int threadNumber;
  private static int threadCount = 0;
  public SimpleThread() {
    threadNumber = ++threadCount;
    System.out.println("Making " + threadNumber);
  }
  public void run() {
    while(true) {
      System.out.println("Thread " +
        threadNumber + "(" + countDown + ")");
      if(--countDown == 0) return;
    }
  }
  public static void main(String[] args) {
    for(int i = 0; i < 5; i++)
      new SimpleThread().start();
    System.out.println("All Threads Started");
  }
} ///:~ 

A
run( )
method virtually always has some kind of loop that continues until the thread
is no longer necessary, so you must establish the condition on which to break
out of this loop (or, in the case above, simply
return
from
run( )).
Often,
run( )
is cast in the form of an infinite loop, which means that, barring some
external call to
stop( )
or
destroy( )
for that thread, it will run forever (until the program completes).

The
output for one run of this program (it will be different every time) is:

Making 1
Making 2
Making 3
Making 4
Making 5
Thread 1(5)
Thread 1(4)
Thread 1(3)
Thread 1(2)
Thread 2(5)
Thread 2(4)
Thread 2(3)
Thread 2(2)
Thread 2(1)
Thread 1(1)
All Threads Started
Thread 3(5)
Thread 4(5)
Thread 4(4)
Thread 4(3)
Thread 4(2)
Thread 4(1)
Thread 5(5)
Thread 5(4)
Thread 5(3)
Thread 5(2)
Thread 5(1)
Thread 3(4)
Thread 3(3)
Thread 3(2)
Thread 3(1)

You’ll
notice that nowhere in this example is
sleep( )
called, and yet the output indicates that each thread gets a portion of the
CPU’s time in which to execute. This shows that
sleep( ),
while it relies on the existence of a thread in order to execute, is not
involved with either enabling or disabling threading. It’s simply another
method.

Threading
for a responsive interface

Now
it’s possible to solve the problem in
Counter1.java
with
a thread. The trick is to place the subtask – that is, the loop
that’s inside
go( )
– inside the
run( )
method of a thread. When the user presses the
start
button, the thread is started, but then the
creation
of the thread completes, so even though the thread is running, the main job of
the program (watching for and responding to user-interface events) can
continue. Here’s the solution:

//: Counter2.java
// A responsive user interface with threads
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
 
class SeparateSubTask extends Thread {
  private int count = 0;
  private Counter2 c2;
  private boolean runFlag = true;
  public SeparateSubTask(Counter2 c2) {
    this.c2 = c2;
    start();
  }
  public void invertFlag() { runFlag = !runFlag;}
  public void run() {
    while (true) {
     try {
      sleep(100);
     } catch (InterruptedException e){}
     if(runFlag)
       c2.t.setText(Integer.toString(count++));
    }
  }
}
 
public class Counter2 extends Applet {
  TextField t = new TextField(10);
  private SeparateSubTask sp = null;
  private Button
    onOff = new Button("Toggle"),
    start = new Button("Start");
  public void init() {
    add(t);
    start.addActionListener(new StartL());
    add(start);
    onOff.addActionListener(new OnOffL());
    add(onOff);
  }
  class StartL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      if(sp == null)
        sp = new SeparateSubTask(Counter2.this);
    }
  }
  class OnOffL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      if(sp != null)
        sp.invertFlag();
    }
  }
  public static void main(String[] args) {
    Counter2 applet = new Counter2();
    Frame aFrame = new Frame("Counter2");
    aFrame.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          System.exit(0);
        }
      });
    aFrame.add(applet, BorderLayout.CENTER);
    aFrame.setSize(300,200);
    applet.init();
    applet.start();
    aFrame.setVisible(true);
  }
} ///:~ 

Counter2
is now a straightforward program, whose job is only to set up and maintain the
user interface. But now, when the user presses the
start
button, a method is not called. Instead a thread of class
SeparateSubTask
is created (the constructor starts it, in this case), and then the
Counter2
event loop continues. Note that the handle to the
SeparateSubTask
is stored so that when you press the
onOff
button it can toggle the
runFlag
inside the
SeparateSubTask
object. That thread (when it looks at the flag) can then start and stop itself.
(This could also have been accomplished by making
SeparateSubTask
an inner class.)

The
class
SeparateSubTask
is a simple extension of
Thread
with a constructor (that stores the
Counter2
handle and then runs the thread by calling
start( ))
and a
run( )
that essentially contains the code from inside
go( )
in
Counter1.java.
Because
SeparateSubTask
knows that it holds a handle to a
Counter2,
it can reach in and access
Counter2’s
TextField
when it needs to.

When
you press the
onOff
button, you’ll see a virtually instant response. Of course, the response
isn’t really instant, not like that of a system that’s driven by
interrupts. The counter stops only when the thread has the CPU and notices that
the flag has changed.


Improving
the code with an inner class

//: Counter2i.java
// Counter2 using an inner class for the thread
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
 
public class Counter2i extends Applet {
  private class SeparateSubTask extends Thread {
    int count = 0;
    boolean runFlag = true;
    SeparateSubTask() { start(); }
    public void run() {
      while (true) {
       try {
        sleep(100);
       } catch (InterruptedException e){}
       if(runFlag)
         t.setText(Integer.toString(count++));
      }
    }
  }
  private SeparateSubTask sp = null;
  private TextField t = new TextField(10);
  private Button
    onOff = new Button("Toggle"),
    start = new Button("Start");
  public void init() {
    add(t);
    start.addActionListener(new StartL());
    add(start);
    onOff.addActionListener(new OnOffL());
    add(onOff);
  }
  class StartL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      if(sp == null)
        sp = new SeparateSubTask();
    }
  }
  class OnOffL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      if(sp != null)
        sp.runFlag = !sp.runFlag; // invertFlag();
    }
  }
  public static void main(String[] args) {
    Counter2i applet = new Counter2i();
    Frame aFrame = new Frame("Counter2i");
    aFrame.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          System.exit(0);
        }
      });
    aFrame.add(applet, BorderLayout.CENTER);
    aFrame.setSize(300,200);
    applet.init();
    applet.start();
    aFrame.setVisible(true);
  }
} ///:~ 

Also,
notice that
SeparateSubTask’s
constructor has been simplified – now it only starts the thread. The
handle to the
Counter2i
object is still being captured as in the previous version, but instead of doing
it by hand and referencing the outer object by hand, the inner class mechanism
takes care of it automatically. In
run( ),
you can see that
t
is simply accessed, as if it were a field of
SeparateSubTask.
The
t
field in the parent class can now be made
private
since
SeparateSubTask
can access it without getting any special permission – and it’s
always good to make fields “as private as possible” so they cannot
be accidentally changed by forces outside your class.

Combining
the thread

with
the main class

In
the example above you can see that the thread class is separate from the
program’s main class. This makes a lot of sense and is relatively easy to
understand. There is, however, an alternate form that you will often see used
that is not so clear but is usually more concise (which probably accounts for
its popularity). This form combines the main program class with the thread
class by making the main program class a thread. Since for a GUI program the
main program class must be inherited from either
Frame
or
Applet,
an interface must be used to paste on the additional functionality. This
interface is called
Runnable,
and it contains the same basic method that
Thread
does. In fact,
Thread
also implements
Runnable,
which specifies only that there be a
run( )
method.

The
use of the combined program/thread is not quite so obvious. When you start the
program, you create an object that’s
Runnable,
but you don’t start the thread. This must be done explicitly. You can see
this in the following program, which reproduces the functionality of
Counter2:

//: Counter3.java
// Using the Runnable interface to turn the 
// main class into a thread.
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
 
public class Counter3
    extends Applet implements Runnable {
  private int count = 0;
  private boolean runFlag = true;
  private Thread selfThread = null;
  private Button
    onOff = new Button("Toggle"),
    start = new Button("Start");
  private TextField t = new TextField(10);
  public void init() {
    add(t);
    start.addActionListener(new StartL());
    add(start);
    onOff.addActionListener(new OnOffL());
    add(onOff);
  }
  public void run() {
    while (true) {
      try {
        selfThread.sleep(100);
      } catch (InterruptedException e){}
      if(runFlag)
        t.setText(Integer.toString(count++));
    }
  }
  class StartL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      if(selfThread == null) {
        selfThread = new Thread(Counter3.this);
        selfThread.start();
      }
    }
  }
  class OnOffL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      runFlag = !runFlag;
    }
  }
  public static void main(String[] args) {
    Counter3 applet = new Counter3();
    Frame aFrame = new Frame("Counter3");
    aFrame.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          System.exit(0);
        }
      });
    aFrame.add(applet, BorderLayout.CENTER);
    aFrame.setSize(300,200);
    applet.init();
    applet.start();
    aFrame.setVisible(true);
  }
} ///:~ 

Now
the
run( )
is inside the class, but it’s still dormant after
init( )
completes. When you press the
start
button, the thread is created (if it doesn’t already exist) in the
somewhat obscure expression:

new
Thread(Counter3.this);

selfThread.start();

This
performs the usual initialization and then calls
run( ).

The
convenient aspect about the
Runnable
interface

is that everything belongs to the same class. If you need to access something,
you simply do it without going through a separate object. The penalty for this
convenience is strict, though – you can have only a single thread running
for that particular object (although you can create more objects of that type,
or create other threads in different classes).

Making
many threads

Consider
the creation of many different threads. You can’t do this with the
previous example, so you must go back to having separate classes inherited from
Thread
to encapsulate the
run( ).
But this is a more general solution and easier to understand, so while the
previous example shows a coding style you’ll often see, I can’t
recommend it for most cases because it’s just a little bit more confusing
and less flexible.

The
following example repeats the form of the examples above with counters and
toggle buttons. But now all the information for a particular counter, including
the button and text field, is inside its own object that is inherited from
Thread.
All the fields in
Ticker
are
private,
which means that the
Ticker
implementation can be changed at will, including the quantity and type of data
components to acquire and display information. When a
Ticker
object is created, the constructor requires a handle to an AWT
Container,
which
Ticker
fills
with its visual components. This way, if you change the visual components, the
code that uses
Ticker
doesn’t need to be modified.

//: Counter4.java
// If you separate your thread from the main
// class, you can have as many threads as you
// want.
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
 
class Ticker extends Thread {
  private Button b = new Button("Toggle");
  private TextField t = new TextField(10);
  private int count = 0;
  private boolean runFlag = true;
  public Ticker(Container c) {
    b.addActionListener(new ToggleL());
    Panel p = new Panel();
    p.add(t);
    p.add(b);
    c.add(p);
  }
  class ToggleL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      runFlag = !runFlag;
    }
  }
  public void run() {
    while (true) {
      if(runFlag)
        t.setText(Integer.toString(count++));
       try {
        sleep(100);
      } catch (InterruptedException e){}
    }
  }
}
 
public class Counter4 extends Applet {
  private Button start = new Button("Start");
  private boolean started = false;
  private Ticker[] s;
  private boolean isApplet = true;
  private int size;
  public void init() {
    // Get parameter "size" from Web page:
    if(isApplet)
      size =
        Integer.parseInt(getParameter("size"));
    s = new Ticker[size];
    for(int i = 0; i < s.length; i++)
      s[i] = new Ticker(this);
    start.addActionListener(new StartL());
    add(start);
  }
  class StartL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      if(!started) {
        started = true;
        for(int i = 0; i < s.length; i++)
          s[i].start();
      }
    }
  }
  public static void main(String[] args) {
    Counter4 applet = new Counter4();
    // This isn't an applet, so set the flag and
    // produce the parameter values from args:
    applet.isApplet = false;
    applet.size =
      (args.length == 0 ? 5 :
        Integer.parseInt(args[0]));
    Frame aFrame = new Frame("Counter4");
    aFrame.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          System.exit(0);
        }
      });
    aFrame.add(applet, BorderLayout.CENTER);
    aFrame.setSize(200, applet.size * 50);
    applet.init();
    applet.start();
    aFrame.setVisible(true);
  }
} ///:~ 

Ticker
contains not only its threading equipment but also the way to control and
display the thread. You can create as many threads as you want without
explicitly creating the windowing components.

In
Counter4
there’s an array of
Ticker
objects called
s.
For maximum flexibility, the size of this array is initialized by reaching out
into the Web page using applet parameters. Here’s what the size parameter
looks like on the page, embedded inside the applet description:

<applet code=Counter4 width=600 height=600>
<param name=size value="20">
</applet>

You’ll
notice that the determination of the size of the array
s
is done inside
init( ),
and not as part of an inline definition of
s.
That is, you
cannot
say as part of the class definition (outside of any methods):

int size = Integer.parseInt(getParameter("size"));
Ticker[] s = new Ticker[size];

You
can compile this, but you’ll get a strange null-pointer exception at run
time. It works fine if you move the
getParameter( )
initialization
inside of
init( ).
The applet
framework performs the necessary startup to grab the parameters before entering
init( ).

Once
the size of the array is established, new
Ticker
objects are created; as part of the
Ticker
constructor the button and text field for each
Ticker
is
added to the applet.

Pressing
the
start
button means looping through the entire array of
Tickers
and calling
start( )
for each one. Remember,
start( )
performs necessary thread initialization and then calls
run( )
for that thread.

The
ToggleL
listener
simply inverts the flag in
Ticker
and when the associated thread next takes note it can react accordingly.

One
value of this example is that it allows you to easily create large sets of
independent subtasks and to monitor their behavior. In this case, you’ll
see that as the number of subtasks gets larger, your machine will probably show
more divergence in the displayed numbers because of the way that the threads
are served.

Daemon
threads

A
“daemon” thread is one that is supposed to provide a general
service in the background as long as the program is running, but is not part of
the essence of the program. Thus, when all of the non-daemon threads complete
the program is terminated. Conversely, if there are any non-daemon threads
still running the program doesn’t terminate. (There is, for instance, a
thread that runs
main( ).)

The
following example demonstrates daemon threads:

//: Daemons.java
// Daemonic behavior
import java.io.*;
 
class Daemon extends Thread {
  private static final int SIZE = 10;
  private Thread[] t = new Thread[SIZE];
  public Daemon() {
    setDaemon(true);
    start();
  }
  public void run() {
    for(int i = 0; i < SIZE; i++)
      t[i] = new DaemonSpawn(i);
    for(int i = 0; i < SIZE; i++)
      System.out.println(
        "t[" + i + "].isDaemon() = "
        + t[i].isDaemon());
    while(true)
      yield();
  }
}
 
class DaemonSpawn extends Thread {
  public DaemonSpawn(int i) {
    System.out.println(
      "DaemonSpawn " + i + " started");
    start();
  }
  public void run() {
    while(true)
      yield();
  }
}
 
public class Daemons {
  public static void main(String[] args) {
    Thread d = new Daemon();
    System.out.println(
      "d.isDaemon() = " + d.isDaemon());
    // Allow the daemon threads to finish
    // their startup processes:
    BufferedReader stdin =
      new BufferedReader(
        new InputStreamReader(System.in));
    System.out.println("Waiting for CR");
    try {
      stdin.readLine();
    } catch(IOException e) {}
  }
} ///:~ 

The
Daemon
thread sets its daemon flag to “true” and then spawns a bunch of
other threads to show that they are also daemons. Then it goes into an infinite
loop that calls
yield( )
to give up control to the other processes. In an earlier version of this
program, the infinite loops would increment
int
counters,
but this seemed to bring the whole program to a stop. Using
yield( )
makes the program quite peppy.

There’s
nothing to keep the program from terminating once
main( )
finishes its job since there are nothing but daemon threads running. So that
you can see the results of starting all the daemon threads,
System.in
is set up to read so the program waits for a carriage return before
terminating. Without this you see only some of the results from the creation of
the daemon threads. (Try replacing the
readLine( )
code with
sleep( )
calls of various lengths to see this behavior.)

More by Author

Must Read