BUY THIS BOOK
Add to Cart

Print Book $39.95


Add to Cart

Print+PDF $51.94

Add to Cart

PDF $31.99

Safari Books Online

What is this?

Add to UK Cart

Print Book £28.50

What is this?

Looking to Reprint or License this content?


Java Threads
Java Threads, Third Edition

By Scott Oaks, Henry Wong
Book Price: $39.95 USD
£28.50 GBP
PDF Price: $31.99

Cover | Table of Contents | Colophon


Table of Contents

Chapter 1: Introduction to Threads
This is a book about using threads in the Java programming language and the Java virtual machine. The topic of threads is very important in Java—so important that many features of the threading system are built into the Java language itself while other features of the threading system are required by the Java virtual machine. Threading is an integral part of using Java.
The concept of threads is not a new one: for some time, many operating systems have had libraries that provide the C programmer a mechanism to create threads. Other languages, such as Ada, have support for threads embedded into the language, much as support for threads is built into the Java language. Nonetheless, until Java came along, the topic of threads was usually considered a peripheral programming topic, one that was only needed in special programming cases.
With Java, things are different: it is impossible to write any but the simplest Java program without introducing the topic of threads. And the popularity of Java ensures that many developers, who might never have considered learning about threading possibilities in a language such as C or C++, need to become fluent in threaded programming.
Futhermore, the Java platform has matured throughout the years. In Java 2 Standard Edition Version 5.0 (J2SE 5.0), the classes available for thread-related programming rival many professional threading packages, mitigating the need to use any commercial library (as was somewhat common in previous releases of Java). So Java developers not only need to become knowledgeable in threaded programming to write basic applications but will want to learn the complete, rich set of classes available for writing complex, commercial-grade applications.
Let's start by defining some terms used throughout this book. Many Java-related terms are used inconsistently in various sources; we endeavor to be consistent in our usage of these terms throughout the book.
Java
First, is the term Java itself. As you know, Java started out as a programming language, and many people today still think of Java as being simply a programming language. But Java is much more than just a programming language: it's also an API specification and a virtual machine specification. So when we say Java, we mean the entire Java platform: the programming language, its APIs, and a virtual machine specification that, taken together, define an entire programming and runtime environment. Often when we say Java, it's clear from the context that we're talking specifically about the programming language, or parts of the Java API, or the virtual machine. The point to remember is that the threading features we discuss in this book derive their properties from all the components of the Java platform taken as a whole. While it's possible to take the Java programming language, directly compile it into assembly code, and run it outside of the virtual machine, such an executable may not necessarily behave the same as the programs we describe in this book.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Java Terms
Let's start by defining some terms used throughout this book. Many Java-related terms are used inconsistently in various sources; we endeavor to be consistent in our usage of these terms throughout the book.
Java
First, is the term Java itself. As you know, Java started out as a programming language, and many people today still think of Java as being simply a programming language. But Java is much more than just a programming language: it's also an API specification and a virtual machine specification. So when we say Java, we mean the entire Java platform: the programming language, its APIs, and a virtual machine specification that, taken together, define an entire programming and runtime environment. Often when we say Java, it's clear from the context that we're talking specifically about the programming language, or parts of the Java API, or the virtual machine. The point to remember is that the threading features we discuss in this book derive their properties from all the components of the Java platform taken as a whole. While it's possible to take the Java programming language, directly compile it into assembly code, and run it outside of the virtual machine, such an executable may not necessarily behave the same as the programs we describe in this book.
Virtual machine, interpreters, and browsers
The Java virtual machine is the code that actually runs a Java program. Its purpose is to interpret the intermediate bytecodes that Java programs are compiled into; the virtual machine is sometimes called the Java interpreter. However, modern virtual machines usually compile the majority of the code they run into native instructions as the program is executing; the result is that the virtual machine does little actual interpretation of code.
Browsers such as Mozilla, Netscape Navigator, Opera, and Internet Explorer all have the capability to run certain Java programs (applets). Historically, these browsers had an embedded virtual machine; today, the standard Java virtual machine runs as a plug-in to these browsers. That means that the threading details of Java-capable browsers are essentially identical to those of a standard Java virtual machine. The one significant area of difference lies in some of the default thread security settings for browsers (see Chapter 13).
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
About the Examples
Full code to run all the examples in this book can be downloaded from http://www.oreilly.com/catalog/jthreads3.
Code is organized by packages in terms of chapter number and example number. Within a chapter, certain classes apply to all examples and are in the chapter-related package (e.g., package javathreads.examples.ch02). The remaining classes are in an example-specific package (e.g., package javathreads.examples.ch02.example1). Package names are shown within the text for all classes.
Examples within a chapter (and often between chapters) tend to be iterative, each one building on the classes of previous examples. Within the text, we use ellipses in code samples to indicate that the code is unchanged from previous examples. For instance, consider this partial example from Chapter 2:
package javathreads.examples.ch02.example2;
...
public class SwingTypeTester extends JFrame {
   ...
   private JButton stopButton;
   ...
   private void initComponents( ) {
       ...
       stopButton = new JButton( );
The package name tells us that this is the second example in Chapter 2. Following the ellipses, we see that there is a new instance variable (stopButton) and some new code added to the initComponents() method.
For reference purposes, we list the examples and their main class at the end of each chapter.
The code examples are written to be compiled and run on J2SE 5.0. We use several new classes of J2SE 5.0 throughout the examples and occasionally use new language features of J2SE 5.0 as well. This means that classes must be compiled with a -source argument:
piccolo% java -source 1.5 javathreads/examples/ch02/example1/*.java
            
While the -source argument is not needed for a great many of our examples, we always use it for consistency.
Running the examples requires using the entire package name for the main class:
piccolo% java javathreads.examples.ch02.example1.SwingTypeTester
            
It is always possible to run each example in this fashion: first compile all the files in the example directory and then run the specific class. This can lead to a lot of typing. To make this easier, we've also supplied an Ant build file that can be used to compile and run all examples.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Why Threads?
The notion of threading is so ingrained in Java that it's almost impossible to write even the simplest programs in Java without creating and using threads. And many of the classes in the Java API are already threaded, so often you are using multiple threads without realizing it.
Historically, threading was first exploited to make certain programs easier to write: if a program can be split into separate tasks, it's often easier to program the algorithm as separate tasks or threads. Programs that fall into this category are typically specialized and deal with multiple independent tasks. The relative rareness of these types of programs makes threading in this category a specialized skill. Often, these programs were written as separate processes using operating system-dependent communication tools such as signals and shared memory spaces to communicate between processes. This approach increased system complexity.
The popularity of threading increased when graphical interfaces became the standard for desktop computers because the threading system allowed the user to perceive better program performance. The introduction of threads into these platforms didn't make the programs any faster, but it created an illusion of faster performance for the user, who now had a dedicated thread to service input or display output.
In the 1990s, threaded programs began to exploit the growing number of computers with multiple processors. Programs that require a lot of CPU processing are natural candidates for this category since a calculation that requires one hour on a single-processor machine could (at least theoretically) run in half an hour on a two-processor machine or 15 minutes on a four-processor machine. All that is required is that the program be written to use multiple threads to perform the calculation.
Although computers with multiple processors have been around for a long time, we're now seeing these machines become cheap enough to be very widely available. The advent of less expensive machines with multiple processors, and of operating systems that provide programmers with thread libraries to exploit those processors, has made threaded programming a hot topic as developers move to extract every benefit from these machines. Until Java, much of the interest in threading centered on using threads to take advantage of multiple processors on a single machine.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Summary
In this chapter, we've provided a basic overview of where we're going in our exploration of threaded programs. Threading is a basic feature of Java, and we've seen some of the reasons why it's more important to Java than to other programming platforms.
In the next few chapters, we look into the basics of thread programming. We start by looking at how threads are created and used in an application.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 2: Thread Creation and Management
In this chapter, we cover all the basics about threads: what a thread is, how threads are created, and some details about the lifecycle of a thread. If you're new to threading, this chapter gives you all the information you need to create some basic threads. Be aware, however, that we take some shortcuts with our examples in this chapter: it's impossible to write a good threaded program without taking into account the data synchronization issues that we discuss in Chapter 3. This chapter gets you started on understanding how threads work; coupled with the next chapter, you'll have the ability to start using threads in your own Java applications.
Let's start by discussing what a thread actually is. A thread is an application task that is executed by a host computer. The notion of a task should be familiar to you even if the terminology is not. Suppose you have a Java program to compute the factorial of a given number:
package javathreads.examples.ch02.example1;

public class Factorial {
    public static void main(String[] args) {
        int n = Integer.parseInt(args[0]);
        System.out.print(n + "! is ");
        int fact = 1;
        while (n > 1)
            fact *= n--;
        System.out.println(fact);
    }
}
When your computer runs this application, it executes a sequence of commands. At an abstract level, that list of commands looks like this:
  • Convert args[0] to an integer.
  • Store that integer in a location called n.
  • Print some text.
  • Store 1 in a location called fact.
  • Test if n is greater than 1.
  • If it is, multiply the value stored in fact by the value stored in n and decrement n by 1.
  • If it isn't, print out the value stored in fact.
Behind the scenes, what happens is somewhat more complicated since the instructions that are executed are actually machine-level assembly instructions; each of our logical steps requires many machine instructions to execute. But the principle is the same: an application is executed as a series of instructions. The execution path of these instructions is a thread.
Consequently, every computer program has at least one thread: the thread that executes the body of the application. In a Java application, that thread is called the main thread, and it begins executing statements with the first statement of the
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
What Is a Thread?
Let's start by discussing what a thread actually is. A thread is an application task that is executed by a host computer. The notion of a task should be familiar to you even if the terminology is not. Suppose you have a Java program to compute the factorial of a given number:
package javathreads.examples.ch02.example1;

public class Factorial {
    public static void main(String[] args) {
        int n = Integer.parseInt(args[0]);
        System.out.print(n + "! is ");
        int fact = 1;
        while (n > 1)
            fact *= n--;
        System.out.println(fact);
    }
}
When your computer runs this application, it executes a sequence of commands. At an abstract level, that list of commands looks like this:
  • Convert args[0] to an integer.
  • Store that integer in a location called n.
  • Print some text.
  • Store 1 in a location called fact.
  • Test if n is greater than 1.
  • If it is, multiply the value stored in fact by the value stored in n and decrement n by 1.
  • If it isn't, print out the value stored in fact.
Behind the scenes, what happens is somewhat more complicated since the instructions that are executed are actually machine-level assembly instructions; each of our logical steps requires many machine instructions to execute. But the principle is the same: an application is executed as a series of instructions. The execution path of these instructions is a thread.
Consequently, every computer program has at least one thread: the thread that executes the body of the application. In a Java application, that thread is called the main thread, and it begins executing statements with the first statement of the main() method of your class. In other programming languages, the starting point may be different, and the terminology may be different, but the basic idea is the same.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Creating a Thread
Threads can be created in two ways: using the Thread class and using the Runnable interface. The Runnable interface (generally) requires an instance of a thread, so we begin with the Thread class.
In this section, we start developing a typing game. The idea of this game is that characters are displayed and the user must type the key corresponding to the character. Through the next few chapters, we add enough logic to score the user's accuracy and timing and provide enough feedback so that the user can improve her typing skills.
For now, we are content to display a random character and display the character the user types in response. This application has two tasks: one task must continually display a random character and then pause for some random period of time. The second task must display characters typed on the keyboard.
Before we delve into the threading aspects of our code, let's look at a few utility classes used in this and subsequent examples. The typing game has two sources for characters: characters that the user types at the keyboard and characters that are randomly generated. Both sources of characters are represented by this interface:
package javathreads.examples.ch02;

public interface CharacterSource {
    public void addCharacterListener(CharacterListener cl);
    public void removeCharacterListener(CharacterListener cl);
    public void nextCharacter( );
}
We want to use the standard Java pattern of event listeners to handle these characters: a listener can register with a particular source and be notified when a new character is available. That requires the typical set of Java classes for a listener pattern, starting with the listener interface:
package javathreads.examples.ch02;

public interface CharacterListener {
    public void newCharacter(CharacterEvent ce);
}
The events themselves are objects of this class:
package javathreads.examples.ch02;

public class CharacterEvent {
    public CharacterSource source;
    public int character;

    public CharacterEvent(CharacterSource cs, int c) {
        source = cs;
        character = c;
    }
}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
The Lifecycle of a Thread
In our example, we gloss over some of the details of how the thread is actually started. We'll discuss that in more depth now and also give details on other lifecycle events of a thread. The lifecycle itself is shown in Figure 2-4. The methods of the Thread class that affect the thread's lifecycle are:
package java.lang;
public class Thread implements Runnable {
    public void start( );
    public void run( );
    public void stop( );   // Deprecated, do not use
    public void resume( );  // Deprecated, do not use
    public void suspend( );    // Deprecated, do not use
    public static void sleep(long millis);
    public static void sleep(long millis, int nanos);
    public boolean isAlive( );
    public void interrupt( );
    public boolean isInterrupted( );
    public static boolean interrupted( );
    public void join( ) throws InterruptedException;
}
Figure 2-4: Lifecycle of a thread
The first phase in this lifecycle is thread creation. Threads are represented by instances of the Thread class, so creating a thread is done by calling a constructor of that class. In our example, we use the simplest constructor available to us. Additional constructors of the Thread class allow you to specify the thread's name or a Runnable object to serve as the thread's target.
All threads have names that serve to identify them in the virtual machine. By default, that name consists of information about the thread: its priority, its thread group, and other thread information we discuss in later chapters. If you like, you can give a thread a different name, perhaps one that will have meaning to you if you print it out.
We discuss the Runnable interface later in this chapter.
A thread exists once it has been constructed, but at that point it is not executing any code. The thread is in a waiting state.
In this waiting state, other threads can interact with the existing thread object. Various attributes of the waiting thread can be set: its priority, its name, its daemon status, and so on. We'll see examples of these throughout the book, but each of these attributes is set simply by calling a method on the waiting thread. Therefore, even though the thread is waiting, its state may be changed by other threads.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Two Approaches to Stopping a Thread
When you want a thread to terminate based on some condition (e.g., the user has quit the game), you have several approaches available. Here we offer the two most common.
The most common way of stopping a thread is to set some internal flag to signal that the thread should stop. The thread can then periodically query that flag to determine if it should exit.
We can rewrite our RandomCharacterGenerator thread to follow this approach:
package javathreads.examples.ch02.example3;
...
public class RandomCharacterGenerator extends Thread implements CharacterSource {
    ...
    private volatile boolean done = false;
    ...
    public void run( ) {
        while (!done) {
            ...
               }
    }
    public void setDone( ) {
        done = true;
    }
}
Here we've created the boolean flag done to signal the thread that it should quit. Now instead of looping forever, the run() method examines the state of that variable on every loop and returns when the done flag has been set. That terminates the thread.
We must now modify our application to set this flag:
package javathreads.examples.ch02.example3;
...
public class SwingTypeTester extends JFrame implements CharacterSource {
    ...
    private JButton stopButton;
    ...    
    private void initComponents( ) {
        ...
        stopButton = new JButton( );
        stopButton.setLabel("Stop");
        p.add(stopButton);
        ...
        stopButton.addActionListener(new ActionListener( ) {
            public void actionPerformed(ActionEvent evt) {
                startButton.setEnabled(true);
                stopButton.setEnabled(false);
                producer.setDone( );
                feedbackCanvas.setEnabled(false);
            }
        });
        ...
    }
    ...
}
Now we have two buttons: a Start and a Stop button. When the Stop button is pressed, the setDone() method is called, and the next time the RandomCharacterGenerator thread executes the top of its loop, that thread exits. This process also reenables the Start button: we can start a new thread at any time.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
The Runnable Interface
When we talked about creating a thread, we mentioned the Runnable interface (java.lang.Runnable). The Thread class implements this interface, which contains a single method:
package java.lang;
public interface Runnable {
    public void run( );
}
The Runnable interface allows you to separate the implementation of a task from the thread used to run the task. For example, instead of extending the Thread class, our RandomCharacterGenerator class might have implemented the Runnable interface:
package javathreads.examples.ch02.example5;
...
// Note: Use Example 3 as the basis for comparison
public class RandomCharacterGenerator implements Runnable {
    ...
}
This changes the way in which the thread that runs the RandomCharacterGenerator object must be constructed:
package javathreads.examples.ch02.example5;
...
public class SwingTypeTester extends JFrame implements CharacterSource {
    ...
    private void initComponents( ) {
        ...
        startButton.addActionListener(new ActionListener( ) {
            public void actionPerformed(ActionEvent evt) {
                producer = new RandomCharacterGenerator( );
                displayCanvas.setCharacterSource(producer);
                Thread t = new Thread(producer);
                t.start( );
                startButton.setEnabled(false);
                stopButton.setEnabled(true);
                feedbackCanvas.setEnabled(true);
                feedbackCanvas.requestFocus( );
            }
        });
        ...
    }
    ...
}
Now we must construct the thread directly and pass the runnable object (producer) to the thread's constructor. Then we start the thread (instead of starting the runnable object).
This leads to the question of whether you should use the Runnable interface or the Thread class when designing your own application. The answer is yes.
The truth is that sometimes it makes sense to use the Runnable interface and sometimes it makes sense to use the Thread class. The answer depends on whether you would like your new class to inherit behavior from the Thread class or if your class needs to inherit from other classes.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Threads and Objects
Let's talk a little more about how threads interact. Consider the RandomCharacterGenerator thread. We saw how another class (the SwingTypeTester class) kept a reference to that thread and how it continued to call methods on that object.
Although those methods are defined in the RandomCharacterGenerator class, they are not executed by that thread. Instead, methods like the setDone( ) method are executed by the Swing event-dispatching thread as it executes the actionPerformed() method within the SwingTypeTester class. As far as the virtual machine is concerned, the setDone() method is just a series of statements; those statements do not "belong" to any particular thread. Therefore, the event-dispatching thread executes the setDone() method in exactly the same way in which it executes any other method.
This point is often confusing to developers who are new to threads; it can be confusing as well to developers who understand threads but are new to object-oriented programming. In Java, an instance of the Thread class is just an object: it may be passed to other methods, and any thread that has a reference to another thread can execute any method of that other thread's Thread object. The Thread object is not the thread itself; it is instead a set of methods and data that encapsulates information about the thread. And that method and data can be accessed by any other thread.
For a more complex example, examine the AnimatedCharacterCanvas class and determine how many threads execute some of its methods. You should be comfortable with the fact that four different threads use this object. The RandomCharacterGenerator thread invokes the newChar() method on that object. The timing thread invokes the run() method. The setDone() method is invoked by the Swing event-dispatching thread. And the constructor of the class (i.e., the default constructor) is invoked by the main method of the application as it constructs the GUI.
The upshot of this is that you cannot look at any object source code and know which thread is executing its methods or examining its data. You may be tempted to look at a class or an object and wonder which thread is running the code. The answer — even if the code is with a class that extends the
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Summary
In this chapter, we've had our first taste of threads. We've learned that threads are separate tasks executed by a single program. This is the key to thinking about how to design a good multithreaded program: what logical tasks make up your program? How can these tasks be separated to make the program logic easier, or benefit your program by running in parallel? In our case, we have two simple tasks: display a random character and display the key that a user types in response. In later chapters, we add more tasks (and more threads) to this list.
At a programming level, we've learned how to construct, start, pause, and stop threads. We've also learned about the Runnable interface and how that interface allows us a great degree of flexibility in how we develop the class hierarchy for our objects. Tasks can be either Thread objects or Runnable objects associated with a thread. Using the Runnable interface allows more flexibility in how you define your tasks, but both approaches have merit in different situations.
We've also touched on how threads interoperate by calling methods on the same object. The ability of threads to interoperate in this manner includes the ability for them to share data as well as code. That data sharing is key to the benefits of a multithreaded program, but it carries with it some pitfalls. This is covered in the next chapter.
Here are the class names and Ant targets for the examples in this chapter:
Description
Main Java class
Ant target
Factorial Example
javathreads.examples.ch02.example1.Factorial number
ch2-ex1
First Swing Type Tester
javathreads.examples.ch02.example2.SwingTypeTester
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 3: Data Synchronization
In the previous chapter, we covered a lot of ground: we examined how to create and start threads, how to arrange for them to terminate, how to name them, how to monitor their lifecycles, and so on. In the examples of that chapter, however, the threads that we examined were more or less independent: they did not need to share data between them.
There were some exceptions to that last point. In some examples, we needed the ability for one thread to determine whether another was finished with its task (i.e., the done flag). In others, we needed to change a character variable that was used in the animation canvas; this was done by a thread different than the Swing thread that redraws the canvas. We glossed over the details at the time, which may have given the implication that they are minor issues. However, we must understand that when two threads share data, complexities arise. These complexities must be taken into consideration whether we're implementing a large shared database or simply sharing a done flag.
In this chapter, we look at the issue of sharing data between threads. Sharing data between threads can be problematic due to what is known as a race condition between threads that attempt to access the same data more or less simultaneously (i.e., concurrently). In this chapter, we examine the concept of a race condition and mechanisms that solve the race condition. We will see how these mechanisms can be used to coordinate access to data as well as solve some other problems in thread communication.
Let's revisit our AnimatedDisplayCanvas class from the previous chapter:
package javathreads.examples.ch02.example7;
    private volatile boolean done = false;
    private int curX = 0;

public class AnimatedCharacterDisplayCanvas extends CharacterDisplayCanvas 
                 implements CharacterListener, Runnable {
    ...
    public synchronized void newCharacter(CharacterEvent ce) {
        curX = 0;
        tmpChar[0] = (char) ce.character;
        repaint( );
    }

    protected synchronized void paintComponent(Graphics gc) {
        Dimension d = getSize( );
        gc.clearRect(0, 0, d.width, d.height);
        if (tmpChar[0] == 0)
            return;
        int charWidth = fm.charWidth(tmpChar[0]);
        gc.drawChars(tmpChar, 0, 1,
                     curX++, fontHeight);
    }

    public void run( ) {
        while (!done) {
            repaint( );
            try {
                Thread.sleep(100);
            } catch (InterruptedException ie) {
                return;
            }
        }
    }

    public void setDone(boolean b) {
        done = b;
    }
}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
The Synchronized Keyword
Let's revisit our AnimatedDisplayCanvas class from the previous chapter:
package javathreads.examples.ch02.example7;
    private volatile boolean done = false;
    private int curX = 0;

public class AnimatedCharacterDisplayCanvas extends CharacterDisplayCanvas 
                 implements CharacterListener, Runnable {
    ...
    public synchronized void newCharacter(CharacterEvent ce) {
        curX = 0;
        tmpChar[0] = (char) ce.character;
        repaint( );
    }

    protected synchronized void paintComponent(Graphics gc) {
        Dimension d = getSize( );
        gc.clearRect(0, 0, d.width, d.height);
        if (tmpChar[0] == 0)
            return;
        int charWidth = fm.charWidth(tmpChar[0]);
        gc.drawChars(tmpChar, 0, 1,
                     curX++, fontHeight);
    }

    public void run( ) {
        while (!done) {
            repaint( );
            try {
                Thread.sleep(100);
            } catch (InterruptedException ie) {
                return;
            }
        }
    }

    public void setDone(boolean b) {
        done = b;
    }
}
This example has multiple threads. The most obvious is the one that we created and which executes the run() method. That thread is specifically created to wake up every 0.1 seconds to send a repaint request to the system. To fulfill the repaint request, the system—at a later time and in a different thread (the event-dispatching thread, to be precise)—calls the paintComponent() method to adjust and redraw the canvas. This constant adjustment and redrawing is what is seen as animation by the user.
There is no race condition between these threads since no data in this object is shared between them. However, as we mentioned at the end of the last chapter, other threads invoke methods of this object. For example, the newCharacter() method is called from the random character-generating thread (a character source) whenever the character to be typed changes.
In this case, there is a race condition. The thread that calls the newCharacter() method is accessing the same data as the thread that calls the
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
The Volatile Keyword
There is still one more threading issue in this example, and it has to do with the setDone() method. This method is called from the event-dispatching thread when the Stop button is pressed; it is called by an event handler (an actionPerformed() method) that is defined as an inner class to the SwingTypeTester class. The issue here is that this method is executed by the event-dispatching thread and changes data that is being used by another thread: the done flag, which is accessed by the thread of the AnimatedDisplayCanvas class.
So, can't we just synchronize the two methods, just as we did previously? Yes and no. Yes, Java's synchronized keyword allows this problem to be fixed. But no, the techniques that we have learned so far will not work. The reason has to do with the run() method. If we synchronized both the run() and setDone() methods, how would the setDone() method ever execute? The run( ) method does not exit until the done flag is set, but the done flag can't be set because the setDone() method can't execute until the run() method completes.
The problem at this point relates to the scope of the lock: the scope of the run() method is too large. By synchronizing the run() method, the lock is grabbed and never released. There is a way to shrink the scope of a lock by synchronizing only the portion of the run() method that protects the done flag (which we examine later in this chapter). However, there is a more elegant solution in this case.
The setDone() method performs only one operation with the
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
More on Race Conditions
Let's examine a more complex example; so far, we have looked at simple data interaction used either for loop control or for redrawing. In this next iteration of our typing game, we share useful data between the threads in order to calculate additional data needed by the application.
Our application has a display component that presents random numbers and letters and a component that shows what the user typed. While there are data synchronization issues between the threads of this example, there is little interaction between these two actions: the act of typing a letter does not depend on the animation letter that is shown. But now we will develop a scoring system. Users see feedback on whether they correctly typed what was presented. Our new code must make this comparison, and it must make sure that no race condition exists when comparing the data.
To accomplish this, we will introduce a new component, one that displays the user's score, which is based on the number of correct and incorrect responses:
package javathreads.examples.ch03.example1;

import javax.swing.*;
import java.awt.event.*;
import javathreads.examples.ch03.*;

public class ScoreLabel extends JLabel implements CharacterListener {
    
    private volatile int score = 0;
    private int char2type = -1;
    private CharacterSource generator = null, typist = null;

    public ScoreLabel (CharacterSource generator, CharacterSource typist) {
        this.generator = generator;
        this.typist = typist;

        if (generator != null)
            generator.addCharacterListener(this);
        if (typist != null)
             typist.addCharacterListener(this);       
    }

    public ScoreLabel ( ) {
        this(null, null);
    }

    public synchronized void resetGenerator(CharacterSource newGenerator) {
        if (generator != null)
            generator.removeCharacterListener(this);
        generator = newGenerator;
        if (generator != null)
            generator.addCharacterListener(this);        
    }

    public synchronized void resetTypist(CharacterSource newTypist) {
        if (typist != null)
            typist.removeCharacterListener(this);
        typist = newTypist;
        if (typist != null)
            typist.addCharacterListener(this);
    }

    public synchronized void resetScore( ) {
        score = 0;
        char2type = -1;
        setScore( );
    }

    private synchronized void setScore( ) {
        // This method will be explained later in chapter 7
        SwingUtilities.invokeLater(new Runnable( ) {
            public void run( ) {
                setText(Integer.toString(score));
            }
        });
    }

    public synchronized void newCharacter(CharacterEvent ce) {
        // Previous character not typed correctly: 1-point penalty
        if (ce.source == generator) {
            if (char2type != -1) {
                score--;
                setScore( );
            }
            char2type = ce.character;
        }

        // If character is extraneous: 1-point penalty
        // If character does not match: 1-point penalty
        else {
            if (char2type != ce.character) {
                score--;
           } else {
               score++;
               char2type = -1;
           }
           setScore( );
       }
    } 
}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Explicit Locking
The purpose of the synchronized keyword is to provide the ability to allow serialized entrance to synchronized methods in an object. Although almost all the needs of data protection can be accomplished with this keyword, it is too primitive when the need for complex synchronization arises. More complex cases can be handled by using classes that achieve similar functionality as the synchronized keyword. These classes are available beginning in J2SE 5.0, but alternatives for use with earlier versions of Java are shown in Appendix A.
The synchronization tools in J2SE 5.0 implement a common interface: the Lock interface. For now, the two methods of this interface that are important to us are lock( ) and unlock(). Using the Lock interface is similar to using the synchronized keyword: we call the lock() method at the start of the method and call the unlock() method at the end of the method, and we've effectively synchronized the method.
The lock() method grabs the lock. The difference is that the lock can now be more easily envisioned: we now have an actual object that represents the lock. This object can be stored, passed around, and even discarded. As before, if another thread owns the lock, a thread that attempts to acquire the lock waits until the other thread calls the unlock() method of the lock. Once that happens, the waiting thread grabs the lock and returns from the lock( ) method. If another thread then wants the lock, it has to wait until the current thread calls the unlock() method. Let's implement our scoring example using this new tool:
package javathreads.examples.ch03.example2;
...
import java.util.concurrent.*;
import java.util.concurrent.locks.*;

public class ScoreLabel extends JLabel implements CharacterListener {
    ...
    private Lock scoreLock = new ReentrantLock( );
    ...
    public void resetGenerator(CharacterSource newGenerator) {
        try {
            scoreLock.lock( );
            if (generator != null)
                generator.removeCharacterListener(this);

            generator = newGenerator;
            if (generator != null)
                generator.addCharacterListener(this);
        } finally {
            scoreLock.unlock( );
        }
    }

    public void resetTypist(CharacterSource newTypist) {
        try {
            scoreLock.lock( );
            if (typist != null)
                typist.removeCharacterListener(this);

            typist = newTypist;
            if (typist != null)
                typist.addCharacterListener(this);
        } finally {
            scoreLock.unlock( );
        }
    }

    public void resetScore( ) {
        try {
            scoreLock.lock( );
            score = 0;
            char2type = -1;
            setScore( );
        } finally {
            scoreLock.unlock( );
        }
    }

    private void setScore( ) {
        // This method will be explained later in chapter 7
        SwingUtilities.invokeLater(new Runnable( ) {
            public void run( ) {
                setText(Integer.toString(score));
            }
        });
    }

    public void newCharacter(CharacterEvent ce) {
        try {
            scoreLock.lock( );
            // Previous character not typed correctly: 1-point penalty
            if (ce.source == generator) {
                if (char2type != -1) {
                    score--;
                    setScore( );
                }
                char2type = ce.character;
            }

            // If character is extraneous: 1-point penalty
            // If character does not match: 1-point penalty
            else {
                if (char2type != ce.character) {
                    score--;
                } else {
                    score++;
                    char2type = -1;
                }
                setScore( );
            }
        } finally {
            scoreLock.unlock( );
        }
    } 
}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Lock Scope
Since we now have t he lock-related classes available in our arsenal, many of our earlier questions can now be addressed. Let's begin looking at the issue of lock scope by modifying our ScoreLabel class:
package javathreads.examples.ch03.example3;
...
public class ScoreLabel extends JLabel implements CharacterListener {
    ...
    public void newCharacter(CharacterEvent ce) {
        if (ce.source == generator) {
            try {
                scoreLock.lock( );
                // Previous character not typed correctly: 1-point penalty
                if (char2type != -1) {
                    score--;
                    setScore( );
                }
                char2type = ce.character;
            } finally {
                scoreLock.unlock( );
            }
        }
        // If character is extraneous: 1-point penalty
        // If character does not match: 1-point penalty
        else {
            try {
                scoreLock.lock( );
                if (char2type != ce.character) {
                    score--;
                } else {
                    score++;
                    char2type = -1;
                }
                setScore( );
            } finally {
                scoreLock.unlock( );
            }
        }
    } 
}
Since the lock() and unlock() method calls are explicit, we can move them anywhere, establishing any lock scope, from a single line of code to a scope that spans multiple methods and objects. By providing the means of specifying the scope of the lock, we can now move time-consuming and threadsafe code outside of the lock scope. And we can now lock at a scope that is specific to the program design instead of the object layout. In this example, we moved the source check outside of the lock, and we also split the lock in two, one for each of the conditions.
It is possible for the synchronized keyword to lock a block of code within a method. It is also possible for the synchronized keyword to specify the object whose lock is grabbed instead of using the lock of the object that contains the method. Much of what we accomplish with the
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Choosing a Locking Mechanism
If we compare our first implementation of the ScoreLabel class (using synchronized methods) to our second (using an explicit lock), it's easy to conclude that using the explicit lock is not as easy as using the synchronized keyword. With the keyword, we didn't need to create the lock object, we didn't need to call the lock object to grab and release the lock, and we didn't need to worry about exceptions (therefore, we didn't need the try/finally clause). So, which technique should you use? That is up to you as a developer. It is possible to use explicit locking for everything. It is possible to code to just use the synchronized keyword. And it is possible to use a combination of both. For more complex thread programming, however, relying solely on the synchronized keyword becomes very difficult, as we will see.
How are the lock classes related to static methods? For static methods, the explicit locks are actually simpler to understand than the synchronized keyword. Lock objects are independent of the objects (and consequently, methods) that use them. As far as lock objects are concerned, it doesn't matter if the method being executed is static or not. As long as the method has a reference to the lock object, it can acquire the lock. For complex synchronization that involves both static and nonstatic methods, it may be easier to use a lock object instead of the synchronized keyword.
Synchronizing entire methods is the simplest technique, but as we have already mentioned, it is possible that doing so creates a lock whose scope is too large. This can cause many problems, including creating a deadlock situation (which we examine later in this chapter). It may also be inefficient to hold a lock for the section of code where it is not actually needed.
Using the synchronized block mechanism may also be a problem if too many objects are involved. As we shall see, it is also possible to have a deadlock condition if we require too many locks to be acquired. There is also a slight overhead in grabbing and releasing the lock, so it may be inefficient to free a lock just to grab it again a few lines of code later. Synchronized blocks also cannot establish a lock scope that spans multiple methods.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Nested Locks
Our implementation of the newCharacter() method could be refactored into multiple methods. This isolates the generator and typist logic into separate methods, making the code easier to maintain.
package javathreads.examples.ch03.example5;
    ...
    private synchronized void newGeneratorCharacter(int c) {
        if (char2type != -1) {
            score--;
            setScore( );
        }
        char2type = c;
    }

    private synchronized void newTypistCharacter(int c) {
        if (char2type != c) {
            score--;
        } else {
            score++;
            char2type = -1;
        }
        setScore( );
    }

    public synchronized void newCharacter(CharacterEvent ce) {
        // Previous character not typed correctly: 1-point penalty
        if (ce.source == generator) {
           newGeneratorCharacter(ce.character);
        }

        // If character is extraneous: 1-point penalty
        // If character does not match: 1-point penalty
        else {
           newTypistCharacter(ce.character);
        }
    } 
}
The two new methods (newGeneratorCharacter() and newTypistCharacter()) are synchronized because they access the shared state of the object. However, in this case, synchronizing the methods is not technically necessary. Unlike the other methods that access the shared data, these methods are private; they can be called only from other methods of the class. Within the class, they are called only from synchronized methods. So, there is no reason for these methods to acquire the lock because all calls to the method already own the lock. Yet it's still a good idea to synchronize methods like this. Developers who modify this class may not realize that their new code needs to obtain the object lock before calling one of these new methods.
The reason this works is that Java does not blindly grab the lock when it enters synchronized code. If the current thread owns the lock, there is no reason to wait for the lock to be freed or even to grab the lock. Instead, the code in the synchronized section just executes. Furthermore, the system is smart enough to not free the lock if it did not initially grab it upon entering the synchronized section of code. This works because the system keeps track of the number of recursive acquisitions of the lock, finally freeing the lock upon exiting the first method (or block) that acquired the lock. This functionality is called
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Deadlock
We have mentioned deadlock a few times in this chapter, and we'll examine the concept in detail in Chapter 6. For now, we just need to understand what it is and why it is a problem.
Simplistically, deadlock occurs when two or more threads are waiting for two or more locks to be freed and the circumstances in the program are such that the locks are never freed. Interestingly, it is possible to deadlock even if no synchronization locks are involved. A deadlock situation involves threads waiting for conditions; this includes waiting to acquire a lock and also waiting for variables to be in a particular state. On the other hand, it is not possible to deadlock if only one thread is involved, as Java allows nested lock acquisition. If a single user thread deadlocks, a system thread must also be involved.
Let's examine a simple example. To do this, we revisit and break one of our classes—the AnimatedCharacterDisplayCanvas class. This class uses a done flag to determine whether the animation should be stopped. The previous example of this class declares the done flag as volatile. This step was necessary to allow atomic access to the variable to function correctly. In this example, we incorrectly synchronize the methods.
package javathreads.examples.ch03.example6;
...
public class AnimatedCharacterDisplayCanvas extends CharacterDisplayCanvas 
                   implements CharacterListener, Runnable {
    private boolean done = false;
    ...
    protected synchronized void paintComponent(Graphics gc) {
        ...
    }

    public synchronized void run( ) {
        while (!done) {
            repaint( );
            try {
                Thread.sleep(100);
            } catch (InterruptedException ie) {
                return;
            }
        }
    }

    public synchronized void setDone(boolean b) {
        done = b;
    }
}
Two threads are involved here: the thread created by this class and the event-dispatching thread that eventually calls the setDone() method. Only one lock is shared between these threads: the lock attached to the object (the instance of the
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Lock Fairness
The last question we need to address is the question of lock fairness. What if we want locks to be issued in a fair fashion? What does it mean to be fair? The ReentrantLock class allows the developer to request that locks be granted fairly. This just means that locks are granted in as close to arrival order as possible. While this is fair for the majority of programs, the definition of "fair" can be much more complex.
Whether locks are granted fairly is subjective (i.e., it is measured by the user's perceptions or other relative means) and can be dependent on particular needs of the program. This means that fairness is based on the algorithm of the program and only minimally based on the synchronization construct that the program uses. In other words, achieving total fairness is dependent on the needs of the program. The best that the threading library can accomplish is to grant locks in a fashion that is specified and consistent.
How should locks be granted with explicit locks? One possibility is that locks should be granted on a first-come-first-served basis. Another is they should be granted in an order that permits servicing the maximum number of requests. For example, if we have multiple requests to make a withdrawal from a bank account, perhaps the smaller withdrawal requests should be accepted first or perhaps deposits should have priority over withdrawals. A third view is that locks should be granted in a fashion that is best for the platform — regardless of whether it is for a banking application, a golfing application, or our typing application.
The behavior of synchronization (using the synchronized keyword or explicit locks) is closest to the last view. Java synchronization primitives are not designed to grant locks for a particular situation — they are part of a general purpose threads library. So, there is no reason that the locks should be granted based on arrival order. Locks are granted based on implementation-specific behavior of the underlying threading system, but it is possible to base the lock acquisitions of the ReentrantLock class on arrival order.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Summary
In this chapter, we've introduced the synchronized keyword of the Java language. This keyword allows us to synchronize methods and blocks of code. We've also examined the basic synchronization classes provided by the Java class library — the ReentrantLock class and the Lock interface. These classes allow us to lock objects across methods and to acquire and release the lock at will based on external events. They also provide features such as testing to see if the lock is available, placin