Home » Java » Desktop Java » Multi-threading in Java Swing with SwingWorker

About John Purcell

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 our best selling eBooks for FREE!

1. JPA Mini Book

2. JVM Troubleshooting Guide

3. JUnit Tutorial for Unit Testing

4. Java Annotations Tutorial

5. Java Interview Questions

6. Spring Interview Questions

7. Android UI Design

and many more ....

 

15 comments

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

  2. 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. Thread.sleep(1000); why the thread need to sleep? …

    • 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. I found this easier to understand than Oracle’s own SwingWorker tutorial, cheers!

  5. 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. Hi!
    Thanks for sharing your knowledge.
    This is a very good article about SwingWorkers.

  7. Superb tutorial thank you.

  8. nice simplicity at its best :)

  9. Great explanation! Thanks a lot!

  10. 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

    • 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.

  11. 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 JButton startButton = new JButton(“Start”);

    private JButton CancelButton = new JButton(“Cancel”);

    private SwingWorker myWorker = null;

    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 = 1;
    gc.gridy = 0;
    gc.weightx = 1;
    gc.weighty = 1;
    add(countLabel1, gc);

    gc.gridx = 1;
    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);

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

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

    start();
    }
    });

    CancelButton.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent arg0) {

    myWorker.cancel(true);
    }
    });

    setSize(400, 400);
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    // position in middle of the screen:
    this.setLocationRelativeTo(null);
    setVisible(true);
    }

    private void start() {

    // SwingWorker worker = new SwingWorker() {
    myWorker = new SwingWorker() {

    @Override
    protected Boolean doInBackground() throws Exception {

    statusLabel.setText(“Now running!”);

    // Simulate doing something useful.
    for (int ii = 0; ii <= 10; ii++) {
    Thread.sleep(1000);

    // The type we pass to publish() is determined
    // by the second template parameter.
    System.out.println("BackgroundThread: " + ii);
    publish(ii);
    }

    // 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 (CancellationException e) {
    System.out.println("I GOT CANCELLED!");
    statusLabel.setText("Cancelled with status: " + false);

    }
    catch (InterruptedException e) {
    // This is thrown if the thread's interrupted.
    System.out.println("I GOT 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 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));
    }

    };

    myWorker.execute();

    }// — start

    public static void main(String[] args) {

    SwingUtilities.invokeLater(new Runnable() {

    @Override
    public void run() {

    new MainFrame(“SwingWorker Demo”);
    }
    });
    }

    }// — MainFrame

Leave a Reply

Your email address will not be published. Required fields are marked *

*


Want to take your Java Skills to the next level?
Grab our programming books for FREE!
  • Save time by leveraging our field-tested solutions to common problems.
  • The books cover a wide range of topics, from JPA and JUnit, to JMeter and Android.
  • Each book comes as a standalone guide (with source code provided), so that you use it as reference.
Last Step ...

Where should we send the free eBooks?

Good Work!
To download the books, please verify your email address by following the instructions found on the email we just sent you.