Java Swing Tutorial - Java Swing Thread








Swing's thread safety rule states that once a Swing component has been realized, we must modify or access that component's state on the event dispatch thread.

A component is considered to be realized if it has been painted or it is ready to be painted.

A top-level container in Swing is realized when we call its pack(), setVisible(true), or show() method for the first time.

When a top-level container is realized, all of its children are also realized.

The event dispatch thread is a thread automatically created by the JVM when it detects that it is working with a Swing application. The JVM uses this thread to execute the Swing component's event handlers.

For example, when we click the JButton, the code in the actionPerformed() method is executed by the event dispatch thread.

The following two classes are helper classes used in a Swing application to deal with its threading model. The classes are

  • SwingUtilities
  • SwingWorker

To know if the code is executing in the event dispatch thread, use the static method isEventDispatchThread() of the SwingUtilities class.

It returns true if the code is executing in the event dispatch thread. Otherwise, it returns false.

System.out.println(SwingUtilities.isEventDispatchThread());

We should follow the rules listed below when working with Swing action handler code.

  • add all event handlers to a component at the end of the GUI-building code.
  • run all GUI code on the event dispatch thread by using the invokeLater(Runnable r) method from the SwingUtilities class.

The following code shows the correct way to start a Swing application.

SwingUtilities.invokeLater(() ->  {
    MySwingApp  app = new MySwingApp("A Swing App");
    app.pack();
    app.setVisible(true);
});

The SwingUtilities.invokeLater(Runnable r) method will start the event dispatch thread if it is not already started.

The SwingUtilities.invokeLater() method call returns immediately and the run() method of its Runnable argument is executed asynchronously.

There is another important static method called invokeAndWait(Runnable r) in the SwingUtilities class.

This method is executed synchronously and it does not return until the run() method has finished executing on the event dispatch thread. This method may throw an InterruptedException or InvocationTargetException.

The SwingUtilities.invokeAndWait(Runnable r) method should not be called from the event dispatch thread because it will block event dispatch thread.

We can use the invokeAndWait() method of the SwingUtilities class to start a Swing application instead of the invokeLater() method.

try  {
    SwingUtilities.invokeAndWait(() ->  { 
       JFrame  frame  = new JFrame(); 
       frame.pack(); 
       frame.setVisible(true);
    });
    System.out.println("Swing application  is running...");
}catch (Exception e)  {
    e.printStackTrace();
}




SwingWorker

To perform a time-consuming task in a Swing application, perform long tasks in a separate thread other than the event dispatch thread.

Swing provides a SwingWorker class, which makes it easy to work with multiple threads in a Swing application.

The SwingWorker<T,V> class is declared abstract. The type parameter T is the result type and the type parameter V is the intermediate result type.

We must create your custom class inheriting from it and provide implementation the following methods:

  • doInBackground(): perform a time-consuming task. It is executed in a separate worker thread.
  • process(): called as a result of a publish() method call. This method executes on the event dispatch thread, and we can access Swing component in this method.
  • The publish() method accepts a varargs argument. The process() method passes all arguments to the publish() method packed in a List.
  • done(): When the doInBackground() method finishes, normally or abnormally, the done() method is called on the event dispatch thread. we can access Swing components in this method.
  • execute(): call this method to start executing the task in a separate thread.
  • get(): This method returns the result of the task as returned from the doInBackground() method. It is not suggested to call this method on the event dispatch thread, as it will block all events until it returns.
  • cancel(boolean mayInterruptIfRunning): This method cancels the task if it is still running.
  • isCancelled(): returns true if the process has been cancelled. Otherwise, it returns false.
  • isDone(): It returns true if the task has completed. A task may complete normally or by throwing an exception or by cancellation. Otherwise, it returns false.
import java.awt.BorderLayout;
import java.awt.Container;
import java.util.List;
//from   ww w  .  ja  v  a2s  .co m
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;

class SwingWorkerProcessor extends SwingWorker<Integer, Integer> {
  private final SwingWorkerFrame frame;
  private int iteration;
  private int intervalInMillis;

  public SwingWorkerProcessor(SwingWorkerFrame frame, int iteration,
      int intervalInMillis) {
    this.frame = frame;
    this.iteration = iteration;

    if (this.iteration <= 0) {
      this.iteration = 10;
    }
    this.intervalInMillis = intervalInMillis;
    if (this.intervalInMillis <= 0) {
      this.intervalInMillis = 1000;
    }
  }

  @Override
  protected Integer doInBackground() throws Exception {
    int sum = 0;
    for (int counter = 1; counter <= iteration; counter++) {
      sum = sum + counter;
      this.publish(counter);
      if (Thread.interrupted()) {
        throw new InterruptedException();
      }
      if (this.isCancelled()) {
        break;
      }
      Thread.sleep(intervalInMillis);
    }

    return sum;
  }

  @Override
  protected void process(List<Integer> data) {
    for (int counter : data) {
      frame.updateStatus(counter, iteration);
    }
  }
  @Override
  public void done() {
    try {
      frame.doneProcessing();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}
class SwingWorkerFrame extends JFrame {
  String startMessage = "Please click the   start button...";
  JLabel statusLabel = new JLabel(startMessage);
  JButton startButton = new JButton("Start");
  JButton cancelButton = new JButton("Cancel");
  SwingWorkerProcessor processor;

  public SwingWorkerFrame() {
    this.setDefaultCloseOperation(EXIT_ON_CLOSE);
    Container contentPane = this.getContentPane();
    cancelButton.setEnabled(false);

    contentPane.add(statusLabel, BorderLayout.NORTH);
    contentPane.add(startButton, BorderLayout.WEST);
    contentPane.add(cancelButton, BorderLayout.EAST);

    startButton.addActionListener(e -> startProcessing());
    cancelButton.addActionListener(e -> cancelProcessing());
  }
  public void setButtonStatus(boolean canStart) {
    if (canStart) {
      startButton.setEnabled(true);
      cancelButton.setEnabled(false);
    } else {
      startButton.setEnabled(false);
      cancelButton.setEnabled(true);
    }
  }
  public void startProcessing() {
    setButtonStatus(false);
    processor = new SwingWorkerProcessor(this, 10, 1000);
    processor.execute();
  }
  public void cancelProcessing() {
    processor.cancel(true);
    setButtonStatus(true);
  }
  public void updateStatus(int counter, int total) {
    String msg = "Processing " + counter + "  of  " + total;
    statusLabel.setText(msg);
  }
  public void doneProcessing()throws Exception {
    if (processor.isCancelled()) {
      statusLabel.setText("Process  cancelled ...");
    } else {
      int sum = processor.get();
      statusLabel.setText("Sum  is " + sum);
      setButtonStatus(true);
    }
  }
}
public class Main{
  public static void main(String[] args) {
    SwingUtilities.invokeLater(() -> {
      SwingWorkerFrame frame = new SwingWorkerFrame();
      frame.pack();
      frame.setVisible(true);
    });
  }
}