APPENDIX B

image

Threading in Swing

Swing is a platform-independent, Model-View-Controller-based GUI toolkit for creating the graphical frontends of Java applications. In this appendix, I first explore Swing’s threading architecture and then explore Swing APIs for avoiding problems when additional threads are used in graphical contexts. Finally, I present a Swing-based slide show application as a significant example of this appendix’s content and as a fun way to end this book.

Image Note  I’ll assume that you have some experience with Swing APIs along with the architecture of a Swing application.

A Single-Threaded Programming Model

Swing follows a single-threaded programming model. It’s designed to be single-threaded instead of multithreaded because experience in the design of multithreaded graphical toolkits has shown that they inevitably lead to deadlock and race conditions. To learn more about these problems, check out the “Why are GUIs Single-threaded?” blog post (http://codeidol.com/java/java-concurrency/GUI-Applications/Why-are-GUIs-Single-threaded/).

The thread that’s used to render graphics and handle events is known as the event-dispatch thread (EDT). The EDT processes events that originate from the underlying Abstract Window Toolkit’s event queue and invokes GUI component (such as button) event listeners, which handle events on this thread. Components even redraw themselves (in response to paint() method calls that result in paintComponent(), paintBorder(), and paintChildren() method calls) on the EDT.

Be careful about how your code interacts with the EDT to ensure that your Swing applications work correctly. There are two rules to remember:

  • Always create Swing GUIs on the EDT.
  • Never delay the EDT.

One consequence of Swing being single-threaded is that you must create a Swing application’s GUI on the EDT only. It’s incorrect to create this GUI on any other thread, including the default main thread that runs a Java application’s main() method.

Most Swing objects (such as javax.swing.JFrame objects, which describe GUI top-level “frame” windows with menu bars and borders) are not thread-safe. Accessing these objects from multiple threads risks thread interference and/or memory inconsistency errors:

  • Thread interference: Two threads are performing two different operations while acting on the same data. For example, one thread reads a long integer counter variable while another thread updates this variable. Because a long integer being read from or written to on a 32-bit machine requires two read/write accesses, it’s possible that the reading thread reads part of this variable’s current value, then the writing thread updates the variable, and then the reading thread reads the rest of the variable. The result is that the reading thread has an incorrect value.
  • Memory inconsistency errors: Two or more threads that are running on different processors or processor cores have inconsistent views of the same data. For example, a writing thread on one processor or core updates a counter variable and then a reading thread on another processor or core reads this variable. However, because a caching mechanism is used to boost performance, neither thread accesses a single copy of the variable in main memory. Instead, each thread accesses its own copy of the variable from local memory (a cache).

How might these problems occur when the GUI isn’t created on the EDT? John Zukowski demonstrates one scenario in his JavaWorld article titled “Swing threading and the event-dispatch thread” (www.javaworld.com/article/2077754/core-java/swing-threading-and-the-event-dispatch-thread.html).

Zukowski presents an example that adds a container listener to a frame window container component. Listener methods are called when a component is added to or removed from the frame. He demonstrates the EDT running code within a listener method before the frame window is realized on the default main thread.

Image Note  To be realized means that a component’s paint() method either has been called or might be called. A frame window is realized by having one of setVisible(true), show(), or pack() called on this container component. After a frame window is realized, all of the components that it contains are also realized. Another way to realize a component is to add it to a container that’s already realized.

After the EDT starts to run in a listener method, and while the default main thread continues to initialize the GUI, components could be created by the default main thread and accessed by the EDT. The EDT might try to access these components before they exist; doing so could crash the application.

Even if the default main thread creates the components before the EDT accesses them from the listener method, the EDT may have an inconsistent view (because of caching) and be unable to access the references to the new components. An application crash (probably a thrown java.lang.NullPointerException object) would most likely occur.

Listing B-1 presents the source code to ViewPage, a Swing application for viewing web page HTML. This application suffers from both problems.

Listing B-1’s main() method creates a GUI consisting of a text field for entering a web page’s URL and a scrollable text area for displaying the page’s HTML. Pressing the Enter key after entering the URL causes ViewPage to fetch and then display the HTML.

Compile Listing B-1 as follows:

javac ViewPage.java

Run the resulting application as follows:

java ViewPage

You should observe the GUI (populated with a sample URL and part of the resulting web page’s HTML) shown in Figure B-1.

9781484216996_AppB-01.jpg

Figure B-1. Entering a URL in a text field and viewing web page output in a scrollable text area

The first problem with this application is that the GUI is created on the default main thread instead of on the EDT. Although you probably won’t encounter a problem when you run ViewPage, there is potential for thread interference and memory inconsistency problems.

The second problem with this application is that the EDT, which runs the action listener in response to pressing Enter on the text field, is delayed by the code that opens an input stream to the URL and reads its content into a string builder. The GUI is unresponsive during this time.

Threading APIs

Swing provides APIs that overcome the aforementioned problems with the EDT. In this section, I introduce you to these APIs. I also introduce you to Swing’s version of a timer, which is considerably different from the Timer Framework that I presented in Chapter 4.

SwingUtilities and EventQueue

The javax.swing.SwingUtilities class provides a collection of static methods that are useful in a Swing context. Three of these methods are especially useful for working with the EDT and avoiding the previous problems:

  • void invokeAndWait(Runnable doRun): Cause doRun.run() to execute synchronously on the EDT. This call blocks until all pending events have been processed and (then) doRun.run() returns. invokeAndWait() throws java.lang.InterruptedException when this method is interrupted while waiting for the EDT to finish executing doRun.run(). It throws java.lang.reflect.InvocationTargetException when an exception is thrown from doRun.run(). invokeAndWait() should be used when an application thread needs to update the GUI from any thread other than the EDT. It shouldn’t be called from the EDT.
  • void invokeLater(Runnable doRun): Cause doRun.run() to be executed asynchronously on the EDT. This happens after all pending events have been processed. invokeLater() should be used when an application thread needs to update the GUI. It can be called from any thread.
  • boolean isEventDispatchThread(): Return true when the invoking thread is the EDT; otherwise, return false.

The invokeAndWait(), invokeLater(), and isEventDispatchThread() methods are wrappers that call the equivalent methods in the java.awt.EventQueue class. Although you can prefix these methods with SwingUtilities, I use EventQueue as the prefix (out of habit).

You typically use invokeLater() to construct a Swing GUI according to the following pattern:

Runnable r = ... // ... refers to the runnable’s anonymous class or lambda
EventQueue.invokeLater(r);

Listing B-2 presents the source code to a second version of ViewPage that uses invokeLater() to construct the Swing GUI on the EDT.

Listing B-2 solves one problem but we still have to prevent the EDT from being delayed. We can solve this problem by creating a worker thread to read the page and use invokeAndWait() to update the scrollable text area with the page content on the EDT. Check out Listing B-3.

I’ve chosen to disable the text field for further input when a page is being obtained and enable it afterward. You can still close the GUI at any time.

Although Listing B-3 solves the unresponsive GUI problem, the solution is somewhat verbose. Fortunately, there is an alternative solution.

SwingWorker

Swing provides the javax.swing.SwingWorker class to accommodate long-running tasks (such as reading URL content) with reduced verbosity. You must subclass this abstract class and override one or more methods to accomplish useful work.

SwingWorker’s generic type is SwingWorker<T, V>. Parameters T and V identify the final and intermediate task result types, respectively.

You override the protected abstract T doInBackground() method to execute a long-running task on a worker thread and return a result of type T (Void is the return type when there is no result). When this method finishes, the protected void done() method is invoked on the EDT. By default, this method does nothing. However, you can override done() to safely update the GUI.

While the task is running, you can periodically publish results to the EDT by invoking the protected void publish(V... chunks) method. These results are retrieved by an overriding protected void process(List<V> chunks) method whose code runs on the EDT. If there are no intermediate results to process, you can specify Void for V (and avoid using the publish() and process() methods).

SwingWorker provides two more methods that you need to know about. First, void execute() schedules the invoking SwingWorker object for execution on a worker thread. Second, T get() waits if necessary for doInBackground() to complete and then returns the final result.

Image Note  SwingWorker’s get() method throws an instance of the java.util.concurrent.ExecutionException class when an exception is thrown while attempting to retrieve the object returned from doInBackground(). It can also throw InterruptedException.

Listing B-4 presents the source code to a final ViewPage application that uses SwingWorker instead of invokeAndWait().

This final version of ViewPage relies on GetHTML, a local SwingWorker subclass that’s declared in the action listener lambda body, to read the web page on a worker thread (keeping the user interface responsive), and update the user interface with the HTML on the EDT (where Swing code must execute).

When the lambda runs (the user presses Enter after entering a URL in the text field), it instantiates GetHTML with the text field’s text (the text field isn’t accessed from the worker thread because Swing is single-threaded) and calls SwingWorker’s execute() method.

execute() causes GetHTML’s overriding doInBackground() method to be called on a worker thread, which populates a java.lang.StringBuilder object with HTML/error text and returns this object. The EDT then calls the overriding done() method, which accesses the StringBuilder object by calling SwingWorker’s get() method and populates the text area with these contents.

Timer

Swing provides the javax.swing.Timer class (as a simplified version of the Timer Framework—see Chapter 4) to periodically execute Swing code on the EDT. It fires an action event to registered listeners after an initial delay and repeatedly thereafter with events separated by between-event delays.

Call the Timer(int delay, ActionListener listener) constructor to create a timer with initial and between-event delays (in milliseconds), and with the initial action listener (which may be null) as the target of events that are sent every delay milliseconds.

The delay parameter value is used as both the initial delay and the between-event delay. You can also set these values separately by calling the void setInitialDelay(int initialDelay) and void setDelay(int delay) methods.

Image Note  Invoke Timer’s void setRepeats(boolean flag) method with a false argument to instruct the timer to send only a single action event.

Call void addActionListener(ActionListener listener) to add another action listener and void removeActionListener(ActionListener listener) to remove the previously registered action listener. Call ActionListener[] getActionListeners() to obtain all registered listeners.

The newly created timer is in its stopped state. To start the timer, call its void start() method. Conversely, you would call void stop() to terminate the timer. You might also want to call boolean isRunning() to determine if the timer is running.

Listing B-5 presents the source code to a Counter application that creates a timer to constantly display a running count via a label.

Listing B-5’s main() method creates a GUI consisting of a label and a Start/Stop button. The label displays the count variable’s current value and the button text alternates between Start and Stop. Clicking the button when it indicates Start causes the timer to start; clicking the button when it indicates Stop causes the timer to stop. The timer action listener increments the count variable and displays its value via the label. The space character that’s appended to count converts the expression to a string and ensures that its rightmost pixels are not cut off.

Compile Listing B-5 as follows:

javac Counter.java

Run the resulting application as follows:

java Counter

Figure B-2 shows the resulting GUI.

9781484216996_AppB-02.jpg

Figure B-2. The panel’s components are horizontally centered

Timer-Based Slide Show

A slide show is a presentation of still images on a projection screen, typically in a prearranged sequence. Each image is usually displayed for at least a few seconds before being replaced by the next image.

A slide show involves a projector, a screen, and slides. The projector contains slides to be projected, the screen displays a projected slide image, and a slide contains an image and other attributes (such as a textual title).

I’ve created a Java application named SlideShow that lets you project arbitrary slideshows. Listing B-6 presents its source code.

Listing B-6 models a slide show in terms of Projector, Screen, Slide, and SlideShow classes. Projector declares several private fields, a Projector(List<Slide> slides, Screen s) constructor for initializing a projector to a java.util.List of Slide objects and a Screen object, a void start() method for starting the projector, and a void stop() method for stopping the projector.

Screen, which subclasses javax.swing.JComponent to make a Screen instance a special kind of Swing component, declares several private fields, a Screen(int width, int height) constructor for instantiating this component to the extents of the screen passed to width and height, and a void drawImage(BufferedImage bi) method for drawing the buffered image passed to this method on the screen’s surface. This class also overrides Dimension getPreferredSize() and void paint(Graphics g) to return the component’s preferred size and to paint its surface.

Slide declares various constants, several private fields, a private Slide(BufferedImage bi, String text) constructor for initializing a Slide object, BufferedImage getBufferedImage() and String getText() getter methods for returning the slide’s buffered image and text, a BufferedImage blend(Slide slide1, Slide slide2, float weight) class method for blending a pair of buffered images to show a transition between slides, and a List<Slide> loadSlides(String imagesPath) class method to load all slide images.

The blend() method extracts the buffered images associated with its slide arguments and blends these images together with the amount of blending determined by weight’s value (which must lie in the range 0.0 through 1.0). The higher the value passed to weight, the more of slide1’s image that contributes to the returned buffered image. After blending the images, blend() blends a pair of text strings over the blended image. The java.awt.AlphaComposite class is used to take care of blending in each case.

I’ve designed blend() to handle a special case where null is passed to slide2. This happens at the beginning of Projector’s start() method where it executes s.drawImage(Slide.blend(slides.get(0), null, 1.0f)); to display the first slide—there’s no transition at this point.

The loadSlides() method looks for a text file named index in the directory identified by this method’s string argument and creates the List of Slides in the order identified by this text file’s contents—you can choose an order to display slides that differs from the order of the image files stored in the directory. Each line is organized as a file name followed by a comma, which is followed by a textual description (such as earth, Terran System). When specifying a file name, you don’t specify a file extension; loadSlides() is hardwired to recognize JPEG files only.

SlideShow declares a main() method that drives this application. This method first verifies that a single command-line argument identifying the slide show directory (a directory containing index and the JPEG files) has been specified. It then invokes loadSlides() to load index and all slide images from this directory. loadSlides() throws java.io.IOException when it’s unable to load an image or when the number of images is less than 2. After all, how can you have a slide show with less than two images?

main() next creates a Screen component object for displaying slide images. It passes the width and height of each slide (actually, the width and height of each slide image) to Screen’s constructor, which ensures that the screen is just large enough to display these slides. (All slide images must have the same width and height, although I don’t enforce this requirement in loadSlides().)

The only remaining significant model object to create is the projector, and main() accomplishes this task by passing the List of Slide objects returned from loadSlides() and the previously created Screen object to Projector’s constructor.

main()’s final task is to cause the GUI to be constructed on the EDT. This thread sets the content pane to the Screen object and invokes Projector’s void start() method to begin the slide show. It also creates a window listener that invokes Projector’s void stop() method when the user attempts to close the window. The window is then disposed.

Projector uses a pair of Timer objects to manage the slide show. The main timer object is responsible for advancing the projector to the next slide, and the subordinate timer object (which is created each time the main timer fires an action event) is responsible for transitioning from the currently displayed slide image to the next slide’s image (with help from blend()).

Each timer instance runs on the EDT. It’s important that the main timer not execute a timer task while the subordinate timer is running. If this rule isn’t followed, the slide show will malfunction. I’ve chosen a period of 3000 milliseconds between successive executions of the main timer task and 10 milliseconds between successive executions of the subordinate timer task, which runs 100 times for an approximate total of 1000 milliseconds. When the subordinate timer task finishes, it stops itself.

Compile Listing B-6 as follows:

javac SlideShow.java

Assuming a Windows operating system, run the resulting application as follows:

java SlideShow ..ss

ss identifies the example solar system slide show (included in this book’s code) and is located in the parent directory of the current directory.

Figure B-3 shows the resulting GUI.

9781484216996_AppB-03.jpg

Figure B-3. SlideShow horizontally centers a slide’s text near the bottom of the slide

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.136.142