Mobile Zone is brought to you in partnership with:

An advent calendar about the Java programming language, posting an interesting technical article from various authors related to Java daily between the 1st and 24th of December, each year. Java is a DZone MVB and is not an employee of DZone and has posted 1 posts at DZone. You can read more from them at their website. View Full User Profile

Multi-threading in Java Swing with SwingWorker

12.04.2012
| 23615 views |
  • submit to reddit

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.

Until next time .... happy coding.
- John

Meta: this post is part of the Java Advent Calendar and is licensed under the Creative Commons 3.0 Attribution license. If you like it, please spread the word by sharing, tweeting, FB, G+ and so on! Want to write for the blog? We are looking for contributors to fill all 24 slot and would love to have your contribution! Contact Attila Balazs to contribute!





Published at DZone with permission of Java Advent, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)