Languages - DevSource
DevSource: Microsoft Developer Resource DevSource Home Sponsored by Microsoft Home Add Ons Architecture Languages Techniques Using VS Forums
Home arrow Languages arrow Page 5 - What's Wrong with This Code? Java Swing and Threading
What's Wrong with This Code? Java Swing and Threading
By Marcus Zarra

Rate This Article: Add This Article To:

What's Wrong with This Code? Java Swing and Threading - ' Race Condition'
( Page 5 of 5 )

: The Answers">

Race Condition: The Answers

This example is quite a bit more involved than the first one. Even though it is more complex, the problem in this code is harder to see, and even harder to reproduce in such a simple example. The answer is: A GUI element is being modified outside of the event thread.

ADVERTISEMENT

But wait. When the example is run, nothing unusual happens. Everything runs as it should. What is the problem?

As discussed earlier, Swing is single threaded. It is also not thread safe. Therefore, altering a GUI element outside of the event thread breaks the single thread model, and puts the GUI at risk.

The problem with this error is that it does not show up when the example is small, and it does not show up consistently. Therefore, even though this example does not make the problem clear, it is there. There is a race condition in this code. It is quite possible that the GUI is in the process of updating itself while a value gets changed outside of the event thread. This causes the GUI to paint itself incorrectly and inconsistently.

To correct this error, all GUI code needs to be executed within the event thread. There are a couple of ways to handle this. The first way is to utilize the javax.swing.SwingUtilities. Inside those utilities are two methods, invokeLater() and invokeAndWait(). Both of those methods accept a java.lang.Runnable object as a parameter. The only difference between them is that invokeAndWait() blocks the current thread until the Runnable is processed; invokeLater() does not block. Using one of these methods in the example code would change the Ticker's run() to look like this:

    private int counter;

    public void run() {
      System.out.println("Starting counter");
      for (counter = 1; counter <= 10; counter++) {
        if (!running) break;
        Runnable r = new Runnable() {
          public void run() {
            progressBar.setValue(counter * 10);
          }
        };
        SwingUtilities.invokeLater(r);
        try {
          Thread.sleep(1000);
        } catch (Exception e) {
          //Expected exception
          System.out.println("Sleep interrupted");
        }
      }
      System.out.println("Counter is finished");
    }

Unfortunately, in this example, utilizing these methods causes two problems.

  1. I am creating and destroying an object on each cycle, which is wasteful.
  2. I have to change the counter to a class variable, so that the Runnable object can access it. That is a bit messy.

Another way to get the JProgressBar update into the event thread where it belongs is to use a javax.swing.Timer object. A Timer object accepts a delay and an ActionListener. The timer then fires the ActionListener based on tthat delay. The code contained within the ActionListener is executed on the event thread, so the single thread model is preserved.

Utilizing the Timer solution changes the example code to look like this:

package com.zarrastudios.example;

import javax.swing.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.*;

public class FrameExample2 extends JFrame {
  private JButton activate, shutdown;
  private JProgressBar progressBar;
  private Timer timer;

  public FrameExample2() {
    super("Frame Example 2");
    initAndLayoutComponents();
    initListeners();
    pack();
  }

  private void initListeners() {
    activate.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent evt) {
        startTimer();
        activate.setEnabled(false);
        shutdown.setEnabled(true);
      }
    });
    shutdown.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent evt) {
        timer.stop();
        activate.setEnabled(true);
        shutdown.setEnabled(false);
      }
    });
    addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent event) {
        System.exit(0);
      }
    });
  }

  private void startTimer() {
    progressBar.setValue(0);
    ActionListener al = new ActionListener() {
      public void actionPerformed(ActionEvent evt) {
        int value = progressBar.getValue();
        value += 10;
        progressBar.setValue(value);
      }
    };
    timer = new Timer(1000, al);
    timer.start();
  }

  private void initAndLayoutComponents() {
    JPanel main = new JPanel(new BorderLayout());
    JPanel north = new JPanel(new FlowLayout());
    JPanel south = new JPanel(new FlowLayout());
    activate = new JButton("Activate");
    shutdown = new JButton("Shutdown");
    shutdown.setEnabled(false);
    progressBar = new JProgressBar();

    north.add(activate);
    north.add(shutdown);

    south.add(progressBar);

    main.add(north, BorderLayout.NORTH);
    main.add(south, BorderLayout.SOUTH);

    setContentPane(main);
  }

  public static void main(String args[]) {
    FrameExample2 fe2 = new FrameExample2();
    fe2.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent event) {
        System.exit(0);
      }
    });
    fe2.setVisible(true);
  }
}

An inner class is no longer needed, since the Timer object handles all of the delay. Since the ActionListener's code is already executing on the event thread, I do not need either the invokeLater() or invokeAndWait() methods from the SwingUtilties... and there is no race condition!

These two examples are, easily, the most common mistakes made when writing Java Swing code. Either of these two coding mistakes can cause the GUI to run in an unpredictable manner and give a poor experience to the user. Combined, as they often are, these flaws create a GUI that is extremely unstable and unpredictable.



 
 
>>> More Languages Articles          >>> More By Marcus Zarra
 



Microsoft's Future: A Chat With Their CTO, Barry Briggs

Play Video >

All Videos >

Julia explores the Robotics Studio!

Read now >

Messages to Bill Gates!

Read now >

View Now
DevSource RSS FEEDS
XML Want an easy way to keep up with breaking tech news? And the Get DevSource headlines delivered to your desktop with RSS.