BUY THIS BOOK

Safari Books Online

What is this?

Looking to Reprint this content?


Java Threads
Java Threads, Second Edition By Scott Oaks, Henry Wong
January 1999
Pages: 336

Cover | Table of Contents | Colophon


Table of Contents

Chapter 1: Introduction to Threading
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 a threaded system are built into the Java language itself, while other features of a threaded 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 with 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, the topic of threads is usually considered a peripheral programming topic, one that's 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 like C or C++ need to become fluent in threaded programming.
We'll start by defining some terms used throughout this book. Many terms surrounding Java are used inconsistently in various sources; we'll endeavor to be consistent in our usage of these terms throughout the book.
Java
First is the term Java itself. As we know, Java started out as a programming language, and many people today 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: a programming language, an API, and a virtual machine specification that, taken together, define an entire programming and runtime environment. Often when we say Java, it's clear from 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
We'll start by defining some terms used throughout this book. Many terms surrounding Java are used inconsistently in various sources; we'll endeavor to be consistent in our usage of these terms throughout the book.
Java
First is the term Java itself. As we know, Java started out as a programming language, and many people today 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: a programming language, an API, and a virtual machine specification that, taken together, define an entire programming and runtime environment. Often when we say Java, it's clear from 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 another term for the Java interpreter, which is the code that ultimately runs Java programs by interpreting the intermediate byte-code format of the Java programming language. The Java interpreter actually comes in three popular forms: the interpreter for developers (called java) that runs programs via the command line or a file manager, the interpreter for end users (called jre ) that is a subset of the developer environment and forms the basis of (among other things) the Java plug-in, and the interpreter that is built into many popular web browsers such as Netscape Navigator, Internet Explorer, HotJava™, and the appletviewer that comes with the Java Developer's Kit. All of these forms are simply implementations of the Java virtual machine, and we'll refer to the Java virtual machine when our discussion applies to any of them. When we use the term
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Thread Overview
This leaves us only one more term to define: what exactly is a thread? The term thread is shorthand for thread of control, and a thread of control is, at its simplest, a section of code executed independently of other threads of control within a single program.
We're all familiar with the use of multitasking operating systems to run multiple programs simultaneously. Each of these programs has at least one thread within it, so at some level, we're already comfortable with the notion of a thread in a single process. The single-threaded process has the following properties, which, as it turns out, are shared by all threads in a program with multiple threads as well:
  • The process begins execution at a well-known point. In programming languages like C and C++ (not to mention Java itself), the thread begins execution at the first statement of the function or method called main() .
  • Execution of the statements follows in a completely ordered, predefined sequence for a given set of inputs. An individual process is single-minded in this regard: it simply executes the next statement in the program.
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 that 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 did create an illusion of faster performance for the user, who now had a dedicated thread to service input or display output.
Recently, there's been a flurry of activity regarding a new use of threaded programs: to exploit the growing number of computers that have 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.
While 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 new machines. Until Java, much of the interest in threading centered around 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
The idea of multiple threads of control within a single program may seem like a new and difficult concept, but it is not. All programs have at least one thread already, and multiple threads in a single program are not radically different from multiple programs within an operating system.
A Java program can contain many threads, all of which may be created without the explicit knowledge of the developer. For now, all you need to consider is that when you write a Java application, there is an initial thread that begins its operation by executing the main() method of your application. When you write a Java applet, there is a thread that is executing the callback methods (init(), actionPerformed(), etc.) of your applet; we speak of this thread as the applet's thread. In either case, your program starts with what you can consider as a single thread. If you want to perform I/O (particularly if the I/O might block), start a timer, or do any other task in parallel with the initial thread, you must start a new thread to perform that task. In the next chapter, we'll examine how to do just that.
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: The Java ThreadingAPI
In this chapter, we will create our own threads. As we shall see, Java threads are easy to use and well integrated with the Java environment.
In the last chapter, we considered threads as separate tasks that execute in parallel. These tasks are simply code executed by the thread, and this code is actually part of our program. The code may download an image from the server or may play an audio file on the speakers or any other task; because it is code, it can be executed by our original thread. To introduce the parallelism we desire, we must create a new thread and arrange for the new thread to execute the appropriate code.
Let's start by looking at the execution of a single thread in the following example:
public class OurClass {
    public void run() {
        for (int I = 0; I < 100; I++) {
            System.out.println("Hello");
        }
    }
}
In this example, we have a class called OurClass. The OurClass class has a single public method called run() that simply writes a string 100 times to the Java console or to the standard output. If we execute this code from an applet as shown here, it runs in the applet's thread:
import java.applet.Applet;

public class OurApplet extends Applet {
    public void init() {
        OurClass oc = new OurClass();
        oc.run();
    }
}
If we instantiate an OurClass object and call its run() method, nothing unusual happens. An object is created, its run() method is called, and the "Hello" message prints 100 times. Just like other method calls, the caller of the run() method waits until the run() method finishes before it continues. If we were to graph an execution of the code, it would look like Figure 2.1.
Figure 2.1: Graphical representation of nonthreaded method execution
What if we want the run() method of OurClass to execute in parallel with the init() and other methods of the applet?
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Threading Using the Thread Class
In the last chapter, we considered threads as separate tasks that execute in parallel. These tasks are simply code executed by the thread, and this code is actually part of our program. The code may download an image from the server or may play an audio file on the speakers or any other task; because it is code, it can be executed by our original thread. To introduce the parallelism we desire, we must create a new thread and arrange for the new thread to execute the appropriate code.
Let's start by looking at the execution of a single thread in the following example:
public class OurClass {
    public void run() {
        for (int I = 0; I < 100; I++) {
            System.out.println("Hello");
        }
    }
}
In this example, we have a class called OurClass. The OurClass class has a single public method called run() that simply writes a string 100 times to the Java console or to the standard output. If we execute this code from an applet as shown here, it runs in the applet's thread:
import java.applet.Applet;

public class OurApplet extends Applet {
    public void init() {
        OurClass oc = new OurClass();
        oc.run();
    }
}
If we instantiate an OurClass object and call its run() method, nothing unusual happens. An object is created, its run() method is called, and the "Hello" message prints 100 times. Just like other method calls, the caller of the run() method waits until the run() method finishes before it continues. If we were to graph an execution of the code, it would look like Figure 2.1.
Figure 2.1: Graphical representation of nonthreaded method execution
What if we want the run() method of OurClass to execute in parallel with the init() and other methods of the applet? In order to do that, we must modify the OurClass class so that it can be executed by a new thread. So the first thing we'll do is make OurClass inherit from the Thread (
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Threading Using the Runnable Interface
As simple as it is to create another thread of control, there is one problem with the technique we've outlined so far. It's caused by the fact that Java classes can inherit their behavior only from a single class, which means that inheritance itself can be considered a scarce resource, and is therefore "expensive" to the developer.
In our example, we are threading a simple loop, so this is not much of a concern. However, if we have a complete class structure that already has a detailed inheritance tree and want it to run in its own thread, we cannot simply make this class structure inherit from the Thread class as we did before. One solution would be to create a new class that inherits from Thread and contains references to the instances of the classes we need. This level of indirection is an annoyance.
The Java language deals with this lack of multiple inheritance by using the mechanism known as interfaces. This mechanism is supported by the Thread class and simply means that instead of inheriting from the Thread class, we can implement the Runnable interface (java.lang.Runnable), which is defined as follows:
public interface Runnable {
     public abstract void run();
}
The Runnable interface contains only one method: the run() method. The Thread class actually implements the Runnable interface; hence, when you inherit from the Thread class, your subclass also implements the Runnable interface. However, in this case we want to implement the Runnable interface without actually inheriting from the Thread class. This is achieved by simply substituting the phrase "implements Runnable" for the phrase "extends Thread"; no other changes are necessary in step 1 of our thread creation process:
public class OurClass implements Runnable {
    public void run() {
        for (int I = 0; I < 100; I++) {
            System.out.println("Hello, from another thread");
        }
    }
}
Step 2 of our thread creation processes has some other changes. Since an instance of the OurClass class is no longer a Thread object, it cannot be treated as one. So in order to create a separate thread of control, an instance of the Thread class is still needed, but it will be instantiated with a reference to our OurClass object. In other words, its usage is slightly more complicated:
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 Life Cycle of a Thread
So far, we have a simple knowledge of working with threads: we know how to use the start() method to start a thread, and how to terminate a thread by arranging for its run() method to complete. We'll now look at two techniques that provide us more information about the thread during its life cycle.
There is a period of time after you call the start() method before the virtual machine can actually start the thread. Similarly, when a thread returns from its run() method, there is a period of time before the virtual machine can clean up after the thread; and if you use the stop() method, there is an even greater period of time before the virtual machine can clean up after the thread.
This delay occurs because it takes time to start or terminate a thread; therefore, there is a transitional period from when a thread is running to when a thread is not running, as shown in Figure 2.3. After the run() method returns, there is a short period of time before the thread stops. If we want to know if the start() method of the thread has been called—or, more usefully, if the thread has terminated—we must use the isAlive() method. This method is used to find out if a thread has actually been started and has not yet terminated:
boolean isAlive()
Determines if a thread is considered alive. By definition, a thread is considered alive from sometime before a thread is actually started to sometime after a thread is actually stopped.
Figure 2.3: Graphical representation of the states of the thread
Let's modify our Animate class to wait until the timer thread stops before finishing:
import java.applet.*;
import java.awt.*;

public class Animate extends Applet {
    int count, lastcount;
    Image pictures[];
    TimerThread timer;

    public void init() {
        lastcount = 10; count = 0;
        pictures = new Image[10];
        MediaTracker tracker = new MediaTracker(this);
        for (int a = 0; a < lastcount; a++) {
            pictures[a] = getImage(
                getCodeBase(), new Integer(a).toString()+".jpeg");
            tracker.addImage(pictures[a], 0);
        }
        tracker.checkAll(true);
    }

    public void start() {
        timer = new TimerThread(this, 1000);
        timer.start();
    }

    public void stop() {
        timer.shouldRun = false;
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Thread Naming
The next topic we will examine concerns the thread support methods that are used mainly for thread "bookkeeping." First, it is possible to assign a String name to the Thread object itself:
void setName(String name)
Assigns a name to the Thread instance.
String getName()
Gets the name of the Thread instance.
The Thread class provides a method that allows us to attach a name to the thread object and a method that allows us to retrieve the name. The system does not use this string for any specific purpose, though the name is printed out by the default implementation of the toString() method of the thread. The developer who assigns the name is free to use this string for any purpose desired. For example, let's assign a name to our TimerThread class:
import java.awt.*;

public class TimerThread extends Thread {
    Component comp;                // Component that needs repainting
    int timediff;                  // Time between repaints of the component
    volatile boolean shouldRun;    // Set to false to stop thread

    public TimerThread(Component comp, int timediff) {
        this.comp = comp;
        this.timediff = timediff;
        shouldRun = true;
        setName("TimerThread(" + timediff + " milliseconds)");
    }

    public void run() {
        while (shouldRun) {
            try {
                comp.repaint();
                sleep(timediff);
            } catch (Exception e) {}
        }
    }
}
In this version of the TimerThread class, we assigned a name to the thread. The name that is assigned is simply "TimerThread" followed by the number of milliseconds used in this timer thread. If the getName() method is later called on this instance, this string value will be returned.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Thread Access
Next, we'll look into several methods that show us information about specific threads.
First, we'll examine the currentThread() method:
static Thread currentThread()
Gets the Thread object that represents the current thread of execution. The method is static and may be called through the Thread class name.
This is a static method of the Thread class, and it simply returns a Thread object that represents the current thread; the current thread is the thread that called the currentThread() method. The object returned is the same Thread object first created for the current thread.
But why is this method important? The Thread object for the current thread may not be saved anywhere, and even if it is, it may not be accessible to the called method. For example, let's look at a class that performs socket I/O and stores the data it reads into an internal buffer. We'll show the full implementation of this class in the next chapter, but for now, we're interested only in its interface:
public class AsyncReadSocket extends Thread {
    StringBuffer result;

    public AsyncReadSocket(String host, int port) {
    // Open a socket to the given host.
    }

    public void run() {
    // Read data from a socket into the result string buffer.
    }

    // Get the string already read from the socket so far.
    // Only allows "Reader" threads to execute this method.
    public String getResult() {
        String reader = Thread.currentThread().getName();
        if (reader.startsWith("Reader")) {
            String retval = result.toString();
            result = new StringBuffer();
            return retval;
        } else {
            return "";
        }
    }
}
To retrieve the data that has been read by this class, you must call 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 Starting, Stopping, and Joining
Consider this revision to the Animate example:
import java.applet.Applet;

public class Animate extends Applet {
    TimerThread t;
    public void start() {
        if (t == null)
            t = new TimerThread(this, 500);
        t.start();
    }

    public void stop() {
        t.shouldRun = false;
        try {
            t.join();
        } catch (InterruptedException e) {}
        // t = null;
     }
}
In our last version of the Animate applet (see Section 2.3," earlier in this chapter), the start() method of the applet created a new TimerThread object and started it. But what if we had only created the TimerThread once? In the example just shown, we once again create a new TimerThread in the start() method of the applet; however, since we know the thread will be stopped in the stop() method, we try to restart the stopped thread in the start() method. In other words, we create the TimerThread only once and use this one thread object to start and stop the animation. By starting and stopping a single TimerThread, we do not need to create a new instance of TimerThread every time the applet is started, and the garbage collector will not need to clean up the TimerThread instance that's left when the applet is stopped and the TimerThread dereferenced.
But will this work? Unfortunately, the answer is no. It turns out that when a thread is stopped, the state of the thread object is set so that it is not restartable. In our case, when we try to restart the thread by calling the TimerThread's start() method, nothing happens. The start() method won't return an exception condition, but the run() method also won't be called. The isAlive() method also won't return true. In other words, never restart a thread. An instance of a thread object should be used once and only once.
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
Here's a list of the methods of the Thread class that we introduced in this chapter:
Thread()
Constructs a thread object using default values for all options.
Thread(Runnable target)
Constructs a new thread object associated with the given Runnable object.
Thread(String name)
Constructs a thread object with a name that is already assigned. This constructor is used when threading by inheritance.
Thread(Runnable target, String name)
Constructs a thread object that is associated with the given Runnable object and is created with a name that is already assigned. This constructor is used when threading by interfaces.
void run()
The method that the newly created thread will execute. Developers should override this method with the code they want the new thread to run; we'll show the default implementation of the run() method a little further on, but it is essentially an empty method.
void start()
Creates a new thread and executes the run() method defined in this thread class.
void stop() (deprecated in Java 2)
Terminates an already running thread.
static void sleep (long milliseconds)
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: Synchronization Techniques
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 life cycles, 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 any data between them.
In this chapter, we look at the issue of sharing data between threads. Sharing data between threads is often hampered due to what is known as a race condition between the threads attempting to access the same data more or less simultaneously. In this chapter, we'll look at the concept of a race condition as well as examining a mechanism that solves race conditions. We will see how this mechanism can be used not only to coordinate access to data, but also for many problems in which synchronization is needed between threads. Before we start, let's introduce a few concepts.
As an application designer for a major bank, we are assigned to the development team for the automated teller machine (ATM). As our first assignment, we are given the task of designing and implementing the routine that allows a user to withdraw cash from the ATM. A first and simple attempt at an algorithm may be as follows (see Figure 3.1 for the flow chart):
  1. Check to make sure that the user has enough cash in the bank account to allow the withdrawal to occur. If the user does not, then go to step 4.
  2. Subtract the amount withdrawn from the user's account.
  3. Dispense the cash from the teller machine to the user.
  4. Print a receipt for the user.
Figure 3.1: Algorithm flow chart for ATM withdrawal
Given this very simple algorithm, an implementation may be as follows:
public class AutomatedTellerMachine extends Teller {
    public void withdraw(float amount) {
        Account a = getAccount();
        if (a.deduct(amount))
            dispense(amount);
        printReceipt();
    }
}
 
public class Account {
    private float total;
    public boolean deduct(float t) {
        if (t <= total) {
            total -= t;
            return true;
        }
        return false;
    }
}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
A Banking Example
As an application designer for a major bank, we are assigned to the development team for the automated teller machine (ATM). As our first assignment, we are given the task of designing and implementing the routine that allows a user to withdraw cash from the ATM. A first and simple attempt at an algorithm may be as follows (see Figure 3.1 for the flow chart):
  1. Check to make sure that the user has enough cash in the bank account to allow the withdrawal to occur. If the user does not, then go to step 4.
  2. Subtract the amount withdrawn from the user's account.
  3. Dispense the cash from the teller machine to the user.
  4. Print a receipt for the user.
Figure 3.1: Algorithm flow chart for ATM withdrawal
Given this very simple algorithm, an implementation may be as follows:
public class AutomatedTellerMachine extends Teller {
    public void withdraw(float amount) {
        Account a = getAccount();
        if (a.deduct(amount))
            dispense(amount);
        printReceipt();
    }
}
 
public class Account {
    private float total;
    public boolean deduct(float t) {
        if (t <= total) {
            total -= t;
            return true;
        }
        return false;
    }
}
Of course, we are assuming that the Teller class and the getAccount(), dispense(), and printReceipt() methods have already been implemented. For our purposes, we are simply examining this algorithm at a high level, so these methods will not be implemented here.
During our testing, we run a few simple and short tests of the routine. These tests involve withdrawing some cash. In certain cases, we withdraw a small amount. In other cases, we withdraw a large amount. We withdraw with enough cash in the account to cover the transaction, and we withdraw without enough cash in the account to cover the transaction. In each case, the code works as desired. Being proud of our routine, we send it to a local branch for beta testing.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Reading Data Asynchronously
Let's look at a complete example. One of the primary uses for threads within a Java program is to read data asynchronously. In this section, we'll develop a class to read a network socket asynchronously.
Why is threading important for I/O? Whether you are reading from or writing to a file or network socket, a common problem exists, namely, that the action of reading or writing depends on other resources. These resources may be other programs; they may be hardware, like the disk or the network; they may be the operating system or browser. These resources may become temporarily unavailable for a variety of reasons: reading from a network socket may involve waiting until the data is available, writing large amounts of data to a file may take a long period of time to complete if the disk is busy with other requests, and so on. Unfortunately, the mechanism to check whether these resources are available does not exist in the Java API. This is particularly a problem for network sockets, where data is likely to take a long time to be transmitted over the network; it is possible for a read from a network socket to wait forever.
The InputStream class does contain the available() method. However, not all input streams support that method, and on a slow network, writing data to a socket is also likely to take a long time. In general, checking for data via the available() method is much less efficient (and much harder to program) than creating a new thread to read the data.
The solution to this problem is to use another thread. Say that we use this new thread in an applet: since this new thread is independent of the applet thread, it can block without hanging the applet. Of course, this causes a new problem: when this thread finally is able to read the data, this data must be returned to the applet thread. Let's take a look at a possible implementation of a generic socket reader class that will read the socket from another thread:
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
A Class to Perform Synchronization
Why do we need a new keyword to solve a race condition? Could we reengineer our algorithms so that race conditions do not exist? Let's see if we can reengineer the AsyncReadSocket class not to have a race condition by using trial and error (obviously not the best programming technique, but one that is useful for our purposes). We'll conclude that it is impossible to solve a race condition without direct support from the virtual machine, because everything that we might try requires two operations: testing and setting variable. Without some process in the virtual machine to ensure that nothing happens to the variable after it is tested and before it is set, a race condition can occur. But the investigation into a possible way to avoid the race condition will provide us with an important tool for our later use—the BusyFlag class.
At first glance, the easiest way to make sure that the two threads do not try to change the result variable, or any buffer at the same time, is to use the concept of a busy flag: if a thread needs to access the result variable, it must set the flag to busy. If the flag is already busy, the thread must wait until the flag is free, at which point it must set the flag to busy. The thread is then free to access the buffer without fear of it being accessed by the other thread. Once the task is completed, the thread must set the flag to not busy.
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 Block
Notice that the original getBusyFlag() method is not declared as synchronized. This is because it's not necessary: getBusyFlag() does not try to access the busyflag variable. Instead, it calls the tryGetBusyFlag() method, which accesses the busyflag and is, of course, synchronized. Let's take another look at the getBusyFlag() method, one that does not call the tryGetBusyFlag() method. Instead, this version gets the busyflag directly:
public synchronized void getBusyFlag() {
     while (true) {
          if (busyflag == null) {
               busyflag = Thread.currentThread();
               break;
          }
          try {
               Thread.sleep(100);
          } catch (Exception e) {}
     }
}
Let's assume that we do not want the inefficiency of an extra method call to the tryGetBusyFlag() method. In our new version of the getBusyFlag() method, we now access the busyflag directly. The getBusyFlag() method simply loops waiting for the flag to be freed, sets the flag, and returns. Since we are now accessing the busyflag directly, we must make the method synchronized or we will have a race condition.
Unfortunately, there is a problem when we declare this method to be synchronized. While declaring the method synchronized prevents other getBusyFlag() and tryGetBusyFlag() methods from being run at the same time (which prevents a race condition), it also prevents the freeBusyFlag() method from running. This means that if the flag is busy when getBusyFlag() is called, getBusyFlag() waits until the flag is freed. Unfortunately, since the freeBusyFlag() method will not run until the getBusyFlag() method frees the object lock, the busyflag will not be freed. This Catch-22 situation is termed deadlock. The deadlock in this case is a problem between a lock and a busyflag. More commonly, deadlock occurs between two or more locks, but the 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!
Nested Locks
Let's examine our BusyFlag class yet again. Suppose we add another method that finds out which thread owns the lock. This getBusyFlagOwner() method simply returns the busyflag, which just so happens to be the thread object that owns the lock. An implementation is as follows:
public synchronized Thread getBusyFlagOwner() {
            
    return busyflag;
}
Furthermore, let's make a modification to the freeBusyFlag() method to use this new getBusyFlagOwner() method:
public synchronized void freeBusyFlag () {
    if (getBusyFlagOwner() == Thread.currentThread()) {
        busyflag = null;
    }
}
In this version of the freeBusyFlag() method, we make a call to the getBusyFlagOwner() method to see if the current thread is the owner before freeing the busyflag. What is interesting here is that both the freeBusyFlag() and the getBusyFlagOwner() methods are synchronized. So what happens? Does the thread hang at the getBusyFlagOwner() method while waiting for the freeBusyFlag() method to free the object lock? If not, and the getBusyFlagOwner() method is allowed to run, what happens when that method completes? Does it free the object lock even though the freeBusyFlag() method still needs it? The answer to all these questions is that it all works the way you want it to.
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
While it is not too difficult to check if a thread already owns a lock before grabbing it, is it possible to prevent deadlock of any kind? Before we try to answer this question, let's look further into just what deadlock is. Simplistically, deadlock is 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 will never be freed. We saw this occur earlier, when we made the getBusyFlag() method synchronized. The fact that the freeBusyFlag() method was also synchronized made it impossible for the busyflag to be freed until the getBusyFlag() method returned. Since the getBusyFlag() method was waiting for the busyflag to be freed, it would wait forever.
That deadlock was caused by an object lock grabbed by the Java synchronization primitive and our own implementation of a lock mechanism, the BusyFlag class. Can this deadlock situation also be caused only with Java's synchronization primitives? The answer to this question is yes; furthermore, it may be difficult to predict deadlock or to detect deadlock when it occurs. Code that runs correctly every time during testing may contain potential deadlocks that occur only under certain conditions or on certain implementations of the Java virtual machine. To better illustrate this problem, let's examine some possible methods that may exist in any database system:
public void removeUseless(Folder file) {
     synchronized (file) {
          if (file.isUseless()) {
               Cabinet directory = file.getCabinet();
               synchronized (directory) {
                    directory.remove(file);
               }
          }
     }
}
Suppose, in some database class, we have a method called removeUseless(). This method is called during the period when the program needs to clean up the database system. It is passed a folder object; this object represents some folder we have in our database system. There is some indication of uselessness that is calculated by the isUseless()
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Return to the Banking Example
So, we just survived the ATM withdrawal problem. It turns out that this problem occurred so infrequently that the total cash involved with the problem transactions was only a few thousand dollars. Luckily, the bank kept records that were good enough to recover the cash. While our manager did not like the fact that we caused a major panic among the upper-level managers, she was somewhat impressed that we were able to track down the problem. While she still does not trust us completely, we still have a job and are able to design and enhance different parts of the ATM system.
The first thing we do is to look at our existing ATM code: we check and double check every piece of code for race conditions, using the synchronized mechanisms that we've learned so far to resolve the problems. Everything seems to be going well until one day the president of the bank receives a phone call from an irate customer. This customer did a balance inquiry at the ATM that showed a balance of $300. Immediately, he attempted to withdraw $290, but could not.
It turns out that in the very short period of time between when the customer checked his balance and attempted to withdraw the money, his wife withdrew $100 from another ATM. Even though the "correct" thing happened, it turned into a big political problem for the bank when the husband threatened to remove his $1 million business account from the bank if the bank "couldn't keep their records straight." So the bank established a new policy that only one ATM could operate on an account at the same time.
This means that we need a new lock scope for the account: the ATM class must be able to lock the account for the duration of a session with a user. This session could comprise transactions that span multiple methods in the ATM class, so the synchronized blocks and synchronized methods that we've learned about so far aren't sufficient to solve this problem: we need a lock that spans multiple methods.
Fortunately, we've already developed the BusyFlag class, so we're in position to solve this problem with little effort:
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Synchronizing Static Methods
Throughout this chapter on synchronization, we kept referring to "obtaining the object lock." But what about static methods? When a synchronized static method is called, which object are we referring to? A static method does not have a concept of the this reference. It is not possible to obtain the object lock of an object that does not exist. So how does synchronization of static methods work? To answer this question, we will introduce the concept of a class lock. Just as there is an object lock that can be obtained for each instance of a class (object), there is a lock that can be obtained for each class. We will refer to this as the class lock . In terms of implementation, there is no such thing as a class lock, but it is a useful concept to help us understand how this all works.
When a static synchronized method is called, the program obtains the class lock before calling the method. This mechanism is identical to the case in which the method is not static; it is just a different lock. The same rule applies: if a synchronized static method calls another synchronized static method of the same class, the system is smart enough to support the nesting of class locks.
But how is the class lock related to the object lock? Apart from the functional relationship between the two locks, they are not operationally related at all. These are two distinct locks. The class lock can be grabbed and released independently of the object lock. If a nonstatic synchronized method calls a static synchronized method, it acquires both locks. Achieving deadlock between these two locks is a little difficult (but not impossible) to accomplish since a static method cannot call a nonstatic method without an object reference.
If a synchronized static method has access to an object reference, can it call synchronized methods of that object or use the object to lock a synchronized block?
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 introduced the synchronized keyword of the Java language. This keyword allows us to synchronize methods and blocks of code.
We've also developed a synchronization primitive of our own: the BusyFlag, which allows us to lock objects across methods and to acquire and release the lock at will based on external events. These features are not available with Java's synchronized keyword, but they are useful in many situations.
This concludes our first look at synchronization. As you can tell, it is one of the most important aspects of threaded programming. Without these techniques, we would not be able to share data correctly between the threads that we create. While these techniques are good enough for many of the programs we will be creating, we introduce other techniques in the next chapter.
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 4: Wait and Notify
In the previous chapter, we took our first look into issues of synchronization. With the synchronization tools introduced, we now are able to have our own threads interoperate and safely share data with each other. It is possible for threads to share data without any race conditions. However, as we shall see, synchronization is more than avoiding race conditions: it includes a thread-based notification system that we'll examine in this chapter.
Having just completed a sweep of all the code in the ATM system—synchronizing any potential problems using the techniques of Chapter 3—we have made the system much more robust. Many little hiccups that used to occur no longer show up. But most important, our BusyFlag class allows us to quickly make the modifications required by our president. The use of the BusyFlag class in this situation allows it to be adopted as a corporate standard and used throughout the whole ATM system.
As far as our manager is concerned, we're heroes—until another problem occurs: it turns out that a portion of the ATM system is facing performance problems. This portion of the system was developed by a coworker who made extensive use of the BusyFlag class. Since it is our class, we are given the task of trying to correct the problem. We start by revisiting the entire BusyFlag class:
public class BusyFlag {
    protected Thread busyflag = null;
    protected int busycount = 0;

    public void getBusyFlag() {
        while (tryGetBusyFlag() == false) {
            try {
                Thread.sleep(100);
            } catch (Exception e) {}
        }
    }

    public synchronized boolean tryGetBusyFlag() {
        if (busyflag == null) {
            busyflag = Thread.currentThread();
            busycount = 1;
            return true;
        }
        if (busyflag == Thread.currentThread()) {
            busycount++;
            return true;
        }
        return false;
    }

    public synchronized void freeBusyFlag () {
        if (getBusyFlagOwner() == Thread.currentThread()) {
            busycount--;
            if (busycount == 0)
                busyflag = null;
        }
    }

    public synchronized Thread getBusyFlagOwner() {
        return busyflag;
    }
}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Back to Work (at the Bank)
Having just completed a sweep of all the code in the ATM system—synchronizing any potential problems using the techniques of Chapter 3—we have made the system much more robust. Many little hiccups that used to occur no longer show up. But most important, our BusyFlag class allows us to quickly make the modifications required by our president. The use of the BusyFlag class in this situation allows it to be adopted as a corporate standard and used throughout the whole ATM system.
As far as our manager is concerned, we're heroes—until another problem occurs: it turns out that a portion of the ATM system is facing performance problems. This portion of the system was developed by a coworker who made extensive use of the BusyFlag class. Since it is our class, we are given the task of trying to correct the problem. We start by revisiting the entire BusyFlag class:
public class BusyFlag {
    protected Thread busyflag = null;
    protected int busycount = 0;

    public void getBusyFlag() {
        while (tryGetBusyFlag() == false) {
            try {
                Thread.sleep(100);
            } catch (Exception e) {}
        }
    }

    public synchronized boolean tryGetBusyFlag() {
        if (busyflag == null) {
            busyflag = Thread.currentThread();
            busycount = 1;
            return true;
        }
        if (busyflag == Thread.currentThread()) {
            busycount++;
            return true;
        }
        return false;
    }

    public synchronized void freeBusyFlag () {
        if (getBusyFlagOwner() == Thread.currentThread()) {
            busycount--;
            if (busycount == 0)
                busyflag = null;
        }
    }

    public synchronized Thread getBusyFlagOwner() {
        return busyflag;
    }
}
Upon revisiting the BusyFlag class, we notice the call to the sleep() method. We originally used this method to avoid eating up too many CPU cycles. At the time, we considered this an open issue. If the getBusyFlag() method sleeps for a long period of time, this might cause the method to wait too long and hence cause a performance problem. Conversely, if the method does not sleep enough, it might eat up too many CPU cycles and hence cause a performance problem. In either case, this has to be fixed: we have to find a way to wait only until the lock is freed. We need 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!
Wait and Notify
Just as each object has a lock that can be obtained and released, each object also provides a mechanism that allows it to be a waiting area. And just like the lock mechanism, the main reason for this mechanism is to aid communication between threads. The idea behind the mechanism is actually simple: one thread needs a certain condition to exist and assumes that another thread will create that condition. When this other thread creates the condition, it notifies the first thread that has been waiting for the condition. This is accomplished with the following methods:
void wait()
Waits for a condition to occur. This is a method of the Object class and must be called from within a synchronized method or block.
void notify()
Notifies a thread that is waiting for a condition that the condition has occurred. This is a method of the Object class and must be called from within a synchronized method or block.
What is the purpose of the wait and notify mechanism, and how does it work? The wait and notify mechanism is also a synchronization mechanism; however, it is more of a communication mechanism: it allows one thread to communicate to another thread that a particular condition has occurred. The wait and notify mechanism does not specify what the specific condition is.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
wait(), notify(), and notifyAll()
What happens when there is more than one thread waiting for the notification? Which thread actually gets the notification when notify() is called? The answer is that it depends: the Java specification doesn't define which thread gets notified. Which thread actually receives the notification varies based on several factors, including the implementation of the Java virtual machine and scheduling and timing issues during the execution of the program. There is no way to determine, even on a single platform, which of multiple threads receives the notification.
There is another method of the Object class that assists us when multiple threads are waiting for a condition:
void notifyAll()
Notifies all the threads waiting on the object that the condition has occurred. This is a method of the Object class and must be called from within a synchronized method or block.
The Object class also provides the notifyAll() method, which helps us in those cases where the program cannot be designed to allow any arbitrary thread to receive the notification. This method is similar to the notify() method, except that all of the threads that are waiting on the object will be notified instead of a single arbitrary thread. Just like the notify() method, the notifyAll() method does not let us decide which threads get notification: they all get notified. By having all the threads receive notification, it is now possible for us to work out a mechanism for the threads to choose among th