Section 2.5. HelloJava4: Netscape s Revenge


2.5. HelloJava4: Netscape's Revenge

We have explored quite a few features of Java with the first three versions of the HelloJava application. But until now, our application has been rather passive; it has been completely event-driven, waiting patiently for events to come its way and responding to the whims of the user. Now our application is going to take some initiativeHelloJava4 will blink![*] Here is the code for our latest version:

[*] The title of this section, "Netscape's Revenge," refers to the infamous <BLINK> HTML tag introduced with an early version of the Netscape web browser.

     //file: HelloJava4.java     import java.awt.*;     import java.awt.event.*;     import javax.swing.*;     public class HelloJava4     {       public static void main( String[] args ) {         JFrame frame = new JFrame( "HelloJava4" );         frame.add( new HelloComponent4("Hello, Java!") );         frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );         frame.setSize( 300, 300 );         frame.setVisible( true );       }     }     class HelloComponent4 extends JComponent         implements MouseMotionListener, ActionListener, Runnable     {       String theMessage;       int messageX = 125, messageY = 95; // Coordinates of the message       JButton theButton;       int colorIndex; // Current index into someColors.       static Color[] someColors = {         Color.black, Color.red, Color.green, Color.blue, Color.magenta };       boolean blinkState;       public HelloComponent4( String message ) {         theMessage = message;         theButton = new JButton("Change Color");         setLayout( new FlowLayout(  ) );         add( theButton );         theButton.addActionListener( this );         addMouseMotionListener( this );         Thread t = new Thread( this );         t.start(  );       }       public void paintComponent( Graphics g ) {         g.setColor(blinkState ? getBackground(  ) : currentColor(  ));         g.drawString(theMessage, messageX, messageY);       }       public void mouseDragged(MouseEvent e) {         messageX = e.getX(  );         messageY = e.getY(  );         repaint(  );       }       public void mouseMoved(MouseEvent e) { }       public void actionPerformed( ActionEvent e ) {         if ( e.getSource(  ) == theButton )           changeColor(  );       }       synchronized private void changeColor(  ) {         if (++colorIndex == someColors.length)           colorIndex = 0;         setForeground( currentColor(  ) );         repaint(  );       }       synchronized private Color currentColor(  ) {         return someColors[colorIndex];       }       public void run(  ) {         try {           while(true) {             blinkState = !blinkState; // Toggle blinkState.             repaint(  ); // Show the change.             Thread.sleep(300);           }         } catch (InterruptedException ie) { }       }     }

Compile and run this version of HelloJava just like the others. You'll see that the text does, in fact, blink. Our apologies if you find this annoyingit's all in the name of education.

2.5.1. Threads

All the changes we've made in HelloJava4 have to do with setting up a separate thread of execution to make the text blink. Java is a multithreaded language, which means there can be many paths of execution, effectively running at the same time. A thread is a separate flow of control within a program. Conceptually, threads are similar to processes. Unlike processes, multiple threads share the same program space, which means that they can share variables and methods (but also have their own local variables). Threads are also quite lightweight in comparison to processes, so it's conceivable for a single application to be running many (perhaps hundreds or thousands) of threads concurrently.

Multithreading provides a way for an application to handle many different tasks at the same time. It's easy to imagine multiple things going on at the same time in an application like a web browser. The user could be listening to an audio clip while scrolling an image; at the same time, the browser can be downloading another image. Multithreading is especially useful in GUI-based applications because it improves the interactive performance of these applications.

Unfortunately for us, programming with multiple threads can be quite a headache. The difficulty lies in making sure routines are implemented so they can be run concurrently, by more than one thread at a time. If a routine changes the value of multiple state variables, for example, it may be important that those changes happen together, without overlapping changes affecting each other. Later in this section, we'll examine briefly the issue of coordinating multiple threads' access to shared data. In other languages, synchronization of threads can be extremely complex and error-prone. You'll see that Java gives you powerful tools that help you deal with many of these problems. See Chapter 9 for a detailed discussion of threads.

The Java runtime system creates and manages a number of threads. (Exactly how varies with the implementation.) We've already mentioned the repaint thread, which manages repaint( ) requests and event processing for GUI components that belong to the java.awt and javax.swing packages. Our example applications have done most of their work in one thread. Methods such as mouseDragged( ) and actionPerformed( ) are invoked by the windowing thread and run by its thread, on its time. Similarly, our HelloComponent constructor runs as part of the main application thread (the main( ) method). This means we are somewhat limited in the amount of processing we do within these methods. If we were, for instance, to go into an endless loop in our constructor, our application would never appear, because it would never finish initializing. If we want an application to perform any extensive processing, such as animation, a lengthy calculation, or communication, we should create separate threads for these tasks.

2.5.2. The Thread Class

As you might have guessed, threads are created and controlled as Thread objects. An instance of the java.lang.Thread class corresponds to a single thread. It contains methods to start, control, and interrupt the thread's execution. Our plan here is to create a Thread object to handle our blinking code. We call the THRead's start( ) method to begin execution. Once the thread starts, it continues to run until it completes its work, we interrupt it, or we stop the application.

So, how do we tell the thread which method to run? Well, the Thread object is rather picky; it always expects to execute a method called run( ) to perform the action of the thread. The run( ) method can, however, with a little persuasion, be located in any class we desire.

We specify the location of the run( ) method in one of two ways. First, the Thread class itself has a method called run( ). One way to execute some Java code in a separate thread is to subclass Thread and override its run( ) method to do our bidding. Invoking the start( ) method of the subclass object causes its run( ) method to execute in a separate thread.

It's not usually desirable to create a subclass of Thread to contain our run( ) method. The Thread class has a constructor that takes an object as its argument. If we create a THRead object using this constructor and call its start( ) method, the THRead executes the run( ) method of the argument object rather than its own. In order to accomplish this, Java needs a guarantee that the object we are passing it does indeed contain a compatible run( ) method. We already know how to make such a guarantee: we use an interface. Java provides an interface named Runnable that must be implemented by any class that wants to become a Thread.

2.5.3. The Runnable Interface

We've implemented the Runnable interface in HelloComponent4. To create a thread, the HelloComponent4 object passes itself (this) to the Thread constructor. This means that HelloComponent4 must implement the Runnable interface by implementing the run( ) method. This method is called automatically when the runtime system needs to start the thread.

We indicate that the class implements the interface in our class declaration:

     public class HelloComponent4         extends JComponent         implements MouseMotionListener, ActionListener, Runnable {...}

At compile time, the Java compiler checks to make sure we abide by this statement. We have carried through by adding an appropriate run( ) method to HelloComponent4. It takes no arguments and returns no value. Our run( ) method accomplishes blinking by changing the color of our text a few times a second. It's a very short routine, but we're going to delay looking at it until we tie up some loose ends in dealing with the Thread itself.

2.5.4. Starting the Thread

We want the blinking to begin when the application starts, so we'll start the thread in the initialization code in HelloComponent4's constructor. It takes only two lines:

     Thread t = new Thread(this);     t.start(  );

First, the constructor creates a new instance of Thread, passing it the object that contains the run( ) method to the constructor. Since HelloComponent4 itself contains our run( ) method, we pass the special variable this to the constructor. this always refers to our object. After creating the new Thread, we call its start( ) method to begin execution. This, in turn, invokes HelloComponent4's run( ) method in the new thread.

2.5.5. Running Code in the Thread

Our run( ) method does its job by setting the value of the variable blinkState. We have added blinkState, a Boolean variable that can have the value TRue or false, to represent whether we are currently blinking on or off:

     boolean blinkState;

A setColor( ) call has been added to our paintComponent( ) method to handle blinking. When blinkState is TRue, the call to setColor( ) draws the text in the background color, making it disappear:

     g.setColor(blinkState ? getBackground(  ) :     currentColor(  ));

Here we are being very terse, using the C language-style ternary operator to return one of two alternative color values based on the value of blinkState. If blinkState is TRue, the value is the value returned by the getBackground( ) method. If it is false, the value is the value returned by currentColor( ).

Finally, we come to the run( ) method itself:

     public void run(  ) {       try {         while( true ) {           blinkState = !blinkState;           repaint(  );           Thread.sleep(300);         }       } catch (InterruptedException ie) {}     }

Basically, run( ) is an infinite while loop, which means the loop runs continuously until the thread is terminated by the application exiting (not a good idea in general, but it works for this simple example).

The body of the loop does three things on each pass:

  • Flips the value of blinkState to its opposite value using the not operator (!)

  • Calls repaint( ) to redraw the text

  • Sleeps for 300 milliseconds (about a third of a second)

sleep( ) is a static method of the Thread class. The method can be invoked from anywhere and has the effect of putting the currently running thread to sleep for the specified number of milliseconds. The effect here is to give us approximately three blinks per second. The TRy/catch construct, described in the next section, traps any errors in the call to the sleep( ) method of the Thread class and, in this case, ignores them.

2.5.6. Exceptions

The try/catch statement in Java handles special conditions called exceptions. An exception is a message that is sent, normally in response to an error, during the execution of a statement or a method. When an exceptional condition arises, an object is created that contains information about the particular problem or condition. Exceptions act somewhat like events. Java stops execution at the place where the exception occurred, and the exception object is said to be thrown by that section of code. Like an event, an exception must be delivered somewhere and handled. The section of code that receives the exception object is said to catch the exception. An exception causes the execution of the instigating section of code to stop abruptly and transfers control to the code that receives the exception object.

The TRy/catch construct allows you to catch exceptions for a section of code. If an exception is caused by any statement inside a try clause, Java attempts to deliver the exception to the appropriate catch clause. A catch clause looks like a method declaration with one argument and no return type.

     try {        ...     } catch ( SomeExceptionType e ) {        ...     }

If Java finds a catch clause with an argument type that matches the type of the exception, that catch clause is invoked. A try clause can have multiple catch clauses with different argument types; Java chooses the appropriate one in a way that is analogous to the selection of overloaded methods. You can catch multiple types of exceptions from a block of code. Depending on the type of exception thrown, the appropriate catch clause is executed.

If there is no try/catch clause surrounding the code, or a matching catch clause is not found, the exception is thrown up to the calling method. If the exception is not caught there, it's thrown up to another level, and so on until the exception is handled or the Java VM prints an error and exits. This provides a very flexible error-handling mechanism so that exceptions in deeply nested calls can bubble up to the surface of the call stack for handling. As a programmer, you need to know what exceptions a particular statement can generate. For this reason, methods in Java are required to declare the exceptions they can throw. If a method doesn't handle an exception itself, it must specify that it can throw that exception so that its calling method knows that it may have to handle it. See Chapter 4 for a complete discussion of exceptions and the try/catch clause.

Why do we need a try/catch clause in the run( ) method? What kind of exception can Thread's sleep( ) method throw and why do we care about it when we don't seem to check for exceptions anywhere else? Under some circumstances, Thread's sleep( ) method can throw an InterruptedException, indicating that it was interrupted by another thread. Since the run( ) method specified in the Runnable interface doesn't declare it can throw an InterruptedException, we must catch it ourselves, or the compiler will complain. The try/catch statement in our example has an empty catch clause, which means that it handles the exception by ignoring it. In this case, our thread's functionality is so simple it doesn't matter if it's interrupted (and it won't be anyway). All the other methods we have used either handle their own exceptions or throw only general-purpose exceptions called RuntimeExceptions that are assumed to be possible everywhere and don't need to be explicitly declared.

2.5.7. Synchronization

At any given time we can have lots of threads running in an application. Unless we explicitly coordinate them, these threads will be executing methods without any regard for what the other threads are doing. Problems can arise when these methods share the same data. If one method is changing the value of some variables at the same time another method is reading these variables, it's possible that the reading thread might catch things in the middle and get some variables with old values and some with new. Depending on the application, this situation could cause a critical error.

In our HelloJava examples, both our paintComponent( ) and mouseDragged( ) methods access the messageX and messageY variables. Without knowing more about the implementation of the Java environment, we have to assume that these methods could conceivably be called by different threads and run concurrently. paintComponent( ) could be called while mouseDragged( ) is in the midst of updating messageX and messageY. At that point, the data is in an inconsistent state and if paintComponent( ) gets lucky, it could get the new x value with the old y value. Fortunately, Swing does not allow this to happen in this case because all event activity is handled by a single thread, and we probably would not even notice if it were to happen in this application anyway. We did, however, see another case, in our changeColor( ) and currentColor( ) methods, that is representative of the potential for a more serious "out of bounds" error.

The synchronized modifier tells Java to acquire a lock for the object that contains the method before executing that method. Only one method in the object can have the lock at any given time, which means that only one synchronized method in that object can be running at a time. This allows a method to alter data and leave it in a consistent state before a concurrently running method is allowed to access it. When the method is done, it releases the lock on the class.

Unlike synchronization in other languages, the synchronized keyword in Java provides locking at the language level. This means there is no way that you can forget to unlock a class. Even if the method throws an exception or the thread is terminated, Java will release the lock. This feature makes programming with threads in Java much easier than in other languages. See Chapter 9 for more details on coordinating threads and shared data.

Whew! Well, it's time to say goodbye to HelloJava. We hope that you have developed a feel for the major features of the Java language and that this will help you as you explore the details of programming with Java. If you are a bit bewildered by some of the material presented here, take heart. We'll be covering all the major topics presented here again in their own chapters throughout the book. This tutorial was meant to be something of a "trial by fire" to get the important concepts and terminology into your brain so that the next time you hear them you'll have a head start.



    Learning Java
    Learning Java
    ISBN: 0596008732
    EAN: 2147483647
    Year: 2005
    Pages: 262

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net