Return to index

Concurrency : Using Threads

 

Topics covered include :

  • What is a Thread?
  • Creating and starting a Thread
  • Safely stopping a thread
  • Using background threads with a Swing GUI
  • The Runnable interface

 

What is a Thread?

A thread can be loosely defined as a separate, concurrent stream of execution. For example, a web browser, when loading a web page, will usually launch a thread to finish loading each image that is in the page; hence you can see multiple images loading at the same time. Threads are useful for splitting tasks and carrying out work that maybe takes some time in the background, keeping the GUI more responsive.

The following code gives an example of a class that extends class Thread :

class SimpleThread extends Thread {

public SimpleThread(String s) {
super(s);
}


public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName());

// pause this thread a random time, between 0 and 1 second
try {
sleep((int)(Math.random() * 1000));
} catch (InterruptedException e) {}
}
System.out.println("DONE! " + getName());
}

}

This class overrides the run() method of class Thread (the default in class Thread does nothing). This method is called when the Thread is started. It loops 10 times printing out the name of the class, finally printing out 'Done' and exiting. While the run() method is executing, the thread is said to be 'active'.

The example creates two instances of SimpleThread with different names, starts them both running and waits for a keypress. Because of the randomised delay element in SimpleThread's run() method, it is random which thread will complete first.

class TwoThreads {
     
 public TwoThreads() {
    Thread thread1=new SimpleThread("Yes");
    Thread thread2=new SimpleThread("No");
 
    thread1.start();
    thread2.start();
 
    waitForKeypress();
 }
 
 
 public static void main (String args[]) {
    new TwoThreads();
 }
 
 
 private static void waitForKeypress() {
   try {
      System.in.read();
   } catch (Exception ex) {}
 }

}

Our second example shows :

  • how to stop a background thread safely
  • how a background thread can safely update a Swing GUI.

class SimpleThread extends Thread {

JLabel label;

private volatile boolean stop=false;

public SimpleThread(String str, JLabel label) {
super(str);
this.label=label;
} public synchronized void setStop(boolean stop) {
this.stop=stop;
}


private void updateLabel(String s) {
final String sf=s;

Runnable doUpdate = new Runnable() {
public void run() {
label.setText(sf);
}
};
SwingUtilities.invokeLater(doUpdate);
}


public void run() {
for (int i = 0; i < 100; i++) {

if (stop) break;

try {
updateLabel(getName());

// pause this thread a random time, between 0 and 1 second
sleep((int)(Math.random() * 1000));
} catch (InterruptedException e) {}
}
System.out.println("DONE! " + getName());
}
}

The Swing GUI compnents were designed to be interacted with from a single thread - the event dispatching thread (which also controls painting of the GUI). The advantages are less overhead in the Swing components by not having to handle multiple thread access safely, and thereby easier for the components to be extended (subclassed), plus events are handled in a predictable order. Therefore, background threads should update Swing components by putting their update / paint requests into the queue of the event-dispatch thread. Swing provides a way to do this conveniently, with two the methods invokeLater() and invokeAnd Wait(). The difference is that invokeAndWait() will wait for the GUI update to complete before continuing. The most generally useful of the two is invokeLater(), which means invoke as soon as possible, but from the event dispatch thread. This is achieved by creating a Runnable object that is placed into the event-dispatch queue.

     Runnable doUpdate = new Runnable() {
public void run() {
label.setText(sf);
}
};
SwingUtilities.invokeLater(doUpdate);

The example creates a situation where we need to stop a background thread, though this is rarely the case in real-life because threads are either designed to do a finite task or to run indefinitely. The initial Thread API had a stop() method but this is now deprecated as this does not allow the thread to cleanup and release any locks it holds and can cause unstable behaviour. In general, the safe way to stop a thread is simply to make sure that it can reach the end of its run() method. In our example we show a way to achieve this which allows an early exit - by simply having the thread periodically check the state of a flag (the boolean 'stop' in the example).

If there is a chance that field 'stop' could be accessed or set from more than one thread either the field should be declared as volatile or it should be made accesible only through a synchronized method.

The following code excerpt shows how the new thread is used. The GUI label is passed to each thread so that the thread can update the label's text and then the threads are started.

       JLabel label=new JLabel("");
label.setAlignmentX(0.5f);
add(label);

JButton stop=new JButton("Stop");
stop.setAlignmentX(0.5f);
add(stop);
stop.addActionListener(this);

thread1=new SimpleThread("Yes", label);
thread2=new SimpleThread("No", label);

thread1.start();
thread2.start();

In the action listener fot the stop button, we can safely stop our threads.

public void actionPerformed(ActionEvent e) {
thread1.setStop(true);
thread2.setStop(true);
}

The Runnable interface

The Runnable interface provides an alternative (and generally better) way to create a new class that can run as a thread. Better, because the class can extend a class other than Thread but still be used as a thread. To implement Runnable means to provide a method with signature public void run().

public class Foo [extends ?] implements Runnable {
     
     public void run {
        //...

     }
}

An instance of the class implementing Runnable can form the content of a Thread.

Foo foo=new Foo();
Thread fooThread=new Thread(foo);
fooThread.start();
Download and run ...

The two example programs are available for download as TJI projects from the 'Resources' page of our web site.

Simply download the zip file and then choose 'Project Import' from the 'Project' menu. Select the zip file and the project will be imported and setup automatically. Now simply click on the 'Run' button to compile and run.

 

Return to index