Desktop Java

Multi-threading in Java Swing with SwingWorker

If you’re writing a desktop or Java Web Start program in Java using Swing, you might feel the need to run some stuff in the background by creating your own threads.

There’s nothing stopping you from using standard multi-threading techniques in Swing, and the usual considerations apply. If you have multiple threads accessing the same variables, you’ll need to use synchronized methods or code blocks (or thread-safe classes like AtomicInteger or ArrayBlockingQueue).
 
 
 
 
 
However, there is a pitfall for the unwary. As with most user interface APIs, you can’t update the user interface from threads you’ve created yourself. Well, as every Java undergraduate knows, you often can, but you shouldn’t. If you do this, sometimes your program will work and other times it won’t.

You can get around this problem by using the specialised SwingWorker class. In this article, I’ll show you how you can get your programs working even if you’re using the Thread class, and then we’ll go on to look at the SwingWorker solution.

For demonstration purposes, I’ve created a little Swing program.

As you can see, it consists of two labels and a start button. At the moment, clicking the start button invokes a handler method which does nothing. Here’s the Java code:

import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
import java.util.concurrent.ExecutionException;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;

public class MainFrame extends JFrame {

 private JLabel countLabel1 = new JLabel('0');
 private JLabel statusLabel = new JLabel('Task not completed.');
 private JButton startButton = new JButton('Start');

 public MainFrame(String title) {
  super(title);

  setLayout(new GridBagLayout());

  countLabel1.setFont(new Font('serif', Font.BOLD, 28));

  GridBagConstraints gc = new GridBagConstraints();

  gc.fill = GridBagConstraints.NONE;

  gc.gridx = 0;
  gc.gridy = 0;
  gc.weightx = 1;
  gc.weighty = 1;
  add(countLabel1, gc);

  gc.gridx = 0;
  gc.gridy = 1;
  gc.weightx = 1;
  gc.weighty = 1;
  add(statusLabel, gc);

  gc.gridx = 0;
  gc.gridy = 2;
  gc.weightx = 1;
  gc.weighty = 1;
  add(startButton, gc);

  startButton.addActionListener(new ActionListener() {
   public void actionPerformed(ActionEvent arg0) {
    start();
   }
  });

  setSize(200, 400);
  setDefaultCloseOperation(EXIT_ON_CLOSE);
  setVisible(true);
 }

 private void start() {

 }

 public static void main(String[] args) {
  SwingUtilities.invokeLater(new Runnable() {

   @Override
   public void run() {
    new MainFrame('SwingWorker Demo');
   }
  });
 }
}

We’re going to add some code into the start() method which is called in response to the start button being clicked.

First let’s try a normal thread.

 private void start() {
  Thread worker = new Thread() {
   public void run() {

    // Simulate doing something useful.
    for(int i=0; i<=10; i++) {
     // Bad practice
     countLabel1.setText(Integer.toString(i));

     try {
      Thread.sleep(1000);
     } catch (InterruptedException e) {

     }
    }

    // Bad practice
    statusLabel.setText('Completed.');
   }
  };

  worker.start();
 }

As a matter of fact, this code seems to work (at least for me anyway). The program ends up looking like this:

This isn’t recommended practice, however. We’re updating the GUI from our own thread, and under some circumstances that will certainly cause exceptions to be thrown.

If we want to update the GUI from another thread, we should use SwingUtilities to schedule our update code to run on the event dispatch thread.

The following code is fine, but ugly as the devil himself.

private void start() {
  Thread worker = new Thread() {
   public void run() {

    // Simulate doing something useful.
    for(int i=0; i<=10; i++) {

     final int count = i;

     SwingUtilities.invokeLater(new Runnable() {
      public void run() {
       countLabel1.setText(Integer.toString(count));
      }
     });

     try {
      Thread.sleep(1000);
     } catch (InterruptedException e) {

     }
    }

    SwingUtilities.invokeLater(new Runnable() {
     public void run() {
      statusLabel.setText('Completed.');
     }
    });

   }
  };

  worker.start();
 }

Surely there must be something we can do to make our code more elegant?

The SwingWorker Class

SwingWorker is an alternative to using the Thread class, specifically designed for Swing. It’s an abstract class and it takes two template parameters, which make it look highly ferocious and puts most people off using it. But in fact it’s not as complex as it seems.

Let’s take a look at some code that just runs a background thread. For this first example, we won’t be using either of the template parameters, so we’ll set them both to Void, Java’s class equivalent of the primitive void type (with a lower-case ‘v’).

Running a Background Task

We can run a task in the background by implementing the doInBackground method and calling execute to run our code.

SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {
   @Override
   protected Void doInBackground() throws Exception {
    // Simulate doing something useful.
    for (int i = 0; i <= 10; i++) {
     Thread.sleep(1000);
     System.out.println('Running ' + i);
    }

    return null;
   }
  };

  worker.execute();

Note that SwingWorker is a one-shot affair, so if we want to run the code again, we’d need to create another SwingWorker; you can’t restart the same one.

Pretty simple, hey? But what if we want to update the GUI with some kind of status after running our code? You cannot update the GUI from doInBackground, because it’s not running in the main event dispatch thread.

But there is a solution. We need to make use of the first template parameter.

Updating the GUI After the Thread Completes

We can update the GUI by returning a value from doInBackground() and then over-riding done(), which can safely update the GUI. We use the get() method to retrieve the value returned from doInBackground()

So the first template parameter determines the return type of both doInBackground() and get().

SwingWorker<Boolean, Void> worker = new SwingWorker<Boolean, Void>() {
   @Override
   protected Boolean doInBackground() throws Exception {
    // Simulate doing something useful.
    for (int i = 0; i <= 10; i++) {
     Thread.sleep(1000);
     System.out.println('Running ' + i);
    }

    // Here we can return some object of whatever type
    // we specified for the first template parameter.
    // (in this case we're auto-boxing 'true').
    return true;
   }

   // Can safely update the GUI from this method.
   protected void done() {

    boolean status;
    try {
     // Retrieve the return value of doInBackground.
     status = get();
     statusLabel.setText('Completed with status: ' + status);
    } catch (InterruptedException e) {
     // This is thrown if the thread's interrupted.
    } catch (ExecutionException e) {
     // This is thrown if we throw an exception
     // from doInBackground.
    }
   }

  };

  worker.execute();

What if we want to update the GUI as we’re going along? That’s what the second template parameter is for.

Updating the GUI from a Running Thread

To update the GUI from a running thread, we use the second template parameter. We call the publish() method to ‘publish’ the values with which we want to update the user interface (which can be of whatever type the second template parameter specifies). Then we override the process() method, which receives the values that we publish.

Actually process() receives lists of published values, because several values may get published before process() is actually called.

In this example we just publish the latest value to the user interface.

  SwingWorker<Boolean, Integer> worker = new SwingWorker<Boolean, Integer>() {
   @Override
   protected Boolean doInBackground() throws Exception {
    // Simulate doing something useful.
    for (int i = 0; i <= 10; i++) {
     Thread.sleep(1000);

     // The type we pass to publish() is determined
     // by the second template parameter.
     publish(i);
    }

    // Here we can return some object of whatever type
    // we specified for the first template parameter.
    // (in this case we're auto-boxing 'true').
    return true;
   }

   // Can safely update the GUI from this method.
   protected void done() {

    boolean status;
    try {
     // Retrieve the return value of doInBackground.
     status = get();
     statusLabel.setText('Completed with status: ' + status);
    } catch (InterruptedException e) {
     // This is thrown if the thread's interrupted.
    } catch (ExecutionException e) {
     // This is thrown if we throw an exception
     // from doInBackground.
    }
   }

   @Override
   // Can safely update the GUI from this method.
   protected void process(List<Integer> chunks) {
    // Here we receive the values that we publish().
    // They may come grouped in chunks.
    int mostRecentValue = chunks.get(chunks.size()-1);

    countLabel1.setText(Integer.toString(mostRecentValue));
   }

  };

  worker.execute();

More …. ? You Want More …. ?

I hope you enjoyed this introduction to the highly-useful SwingWorker class.

You can find more tutorials, including a complete free video course on multi-threading and courses on Swing, Android and Servlets, on my site Cave of Programming.
 

Reference: Multi-threading in Java Swing with SwingWorker from our JCG partner John Purcell at the Java Advent Calendar blog.

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

16 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Dmitri
11 years ago

Very clear explanation! This is exactly what I was looking for, will try to use new swing knowledge :)

anja
10 years ago

hallo sir,
sir, in your code: …. for (int i = 0; i <= 10; i++) …
in a real program what is a number `0` and `10` means … ?

Andy
Andy
9 years ago
Reply to  anja

Sire,

think of 0 meaning you did not eat.
think of 10 means you ate 10 eggs.

Now it is clear to me.

anja
10 years ago

Thread.sleep(1000); why the thread need to sleep? …

aras
aras
9 years ago
Reply to  anja

there is no need. thread just adds your code latency. for instance:
for(int i=0;1==1;i++)
System.out.println(i);
in this code, your pc prints i values as fast as it can. but if you use thread sleep, i will be increased per second and i values will be printed per second like clock. 1000 means 1000milisecond, which means 1 sec

for(int i=0;1==1;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(i);
}

anja
9 years ago
Reply to  aras

thank you aras.

Sirloin
Sirloin
10 years ago

I found this easier to understand than Oracle’s own SwingWorker tutorial, cheers!

Ameya
Ameya
10 years ago

Very lucid and to the point explanation. I had seen quite a few tutorials before this one but found this to be the most useful one. Thanks a lot for taking out time and efforts for this good work.

Cheers,
Ameya.

Andres
Andres
9 years ago

Hi!
Thanks for sharing your knowledge.
This is a very good article about SwingWorkers.

John
John
9 years ago

Superb tutorial thank you.

poovai vadivelan
poovai vadivelan
9 years ago

nice simplicity at its best :)

Rosh
Rosh
9 years ago

Great explanation! Thanks a lot!

James
James
9 years ago

How much data can be stored in chunks? If there is infinite loop in doInBackground(), can chunks get all the data. Namely, is chunks reliable to be buffer?

Thanks,
James

John
9 years ago
Reply to  James

Usually not much … it will only save up the amount of data that has been published since the last GUI update ran, which will never be very much.

PAtrick
PAtrick
8 years ago

Hi John thanks for the excellent explanation sample! Since I had to work on it I thought it might be nice to enrich your sample with even an optin to cancel the background process. so here is your modified sample with a cancel button: import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.List; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.SwingUtilities; import javax.swing.SwingWorker; public class MainFrame extends JFrame { /** */ private static final long serialVersionUID = 1L; private JLabel countLabel1 = new JLabel(“0”); private JLabel statusLabel = new JLabel(“Task not completed.”); private… Read more »

felix
felix
8 years ago

Why does the thread need to sleep… 1000 seconds makes only about 20 minutes… I think it is just a nap. :)

Back to top button