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.

Do you want to know how to develop your skillset to become a Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you two of our best selling eBooks for FREE!

JPA Mini Book

Learn how to leverage the power of JPA in order to create robust and flexible Java applications. With this Mini Book, you will get introduced to JPA and smoothly transition to more advanced concepts.

JVM Troubleshooting Guide

The Java virtual machine is really the foundation of any Java EE platform. Learn how to master it with this advanced guide!

Given email address is already subscribed, thank you!
Oops. Something went wrong. Please try again later.
Please provide a valid email address.
Thank you, your sign-up request was successful! Please check your e-mail inbox.
Please complete the CAPTCHA.
Please fill in the required fields.

10 Responses to "Multi-threading in Java Swing with SwingWorker"

  1. Dmitri says:

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

  2. anja says:

    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 … ?

  3. anja says:

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

    • aras says:

      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);
      }

  4. Sirloin says:

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

  5. Ameya says:

    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.

  6. Andres says:

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

  7. John says:

    Superb tutorial thank you.

  8. poovai vadivelan says:

    nice simplicity at its best :)

Leave a Reply


× seven = 63



Java Code Geeks and all content copyright © 2010-2014, Exelixis Media Ltd | Terms of Use | Privacy Policy | Contact
All trademarks and registered trademarks appearing on Java Code Geeks are the property of their respective owners.
Java is a trademark or registered trademark of Oracle Corporation in the United States and other countries.
Java Code Geeks is not connected to Oracle Corporation and is not sponsored by Oracle Corporation.
Do you want to know how to develop your skillset and become a ...
Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you two of our best selling eBooks for FREE!

Get ready to Rock!
You can download the complementary eBooks using the links below:
Close