[an error occurred while processing this directive] [an error occurred while processing this directive]

The Java Sourcebook

by Ed Anuff, HotWired

The following is unedited text from the Table of Contents, Introduction, and Chapter 12 of The Java Sourcebook. Copyright 1996 John Wiley & Sons, Inc. All rights reserved.

Table of Contents

Introduction

Chapter 1. GETTING READY FOR JAVA

Chapter 2. WEB BROWSING AND JAVA

Chapter 3. UNDERSTANDING JAVA

Chapter 4. BUILDING JAVA APPLICATIONS

Chapter 5. JAVA BASICS

Chapter 6. FLOW OF CONTROL

Chapter 7. OBJECTS AND CLASSES

Chapter 8. INTERFACES

Chapter 9. PACKAGES

Chapter 10. MANAGING DATA WITH OBJECTS

Chapter 11. HANDLING EXCEPTIONS

Chapter 12. THREADS

Chapter 13. APPLET OVERVIEW

Chapter 14. APPLET BASICS

Chapter 15. APPLET USER INTERFACES

Chapter 16. GRAPHICS

Chapter 17. MULTIMEDIA

Appendix A: Package java.app

Appendix B: Package java.awt

Appendix C: Package java.awt.image


Introduction

Thanks for picking up this book. If you're reading this, you're probably excited about learning more about Java, which could be argued is one of the most important things to come to the World Wide Web since its inception. By now, you're probably familiar with the Web and have used a Web browser like Mosaic or Netscape to pull information from anywhere in the world by simply pointing and clicking on hot links.

What makes this possible is that the Web defines a standard method of communication between Web servers and Web browsers called HTTP and a number of standard formats for information that most computers have a reasonable shot at displaying. This basic system has allowed many interesting and exciting Web pages to be built and enjoyed by thousands of net surfers, but as the web has started to grow, people have started to encounter limitations of this system. To understand why, you need to back up a bit and take a look at the elements that make up the Web itself.

Java and the Future of the WWW

The Web at its simplest consists of two programs, the browser (which runs on your computer) and the server (which usually runs on a remote system). Most browsers can really only do a few basic things. A browser can either request information from the server, or ask the server to run special programs called CGI scripts for it. While it would be unfair to call a browser "dumb", most of the interactivity you experience while using the Web beyond simply clicking on hotlinks is the result of CGI scripts being run on remote web servers which use the output of those scripts to decide what to show you or where to take you next.

For example, many pages have clickable graphics images, called image maps, which can be clicked on in order to jump to another web page or cause some other action to occur. When you click on the image map, what happens is that the coordinated of where you clicked within the image as transferred to a CGI script running on the web server. The CGI script looks at these coordinates and decides what page to send back to the browser in response. Considering that often times the web server your browser is talking to can be half a world away, its understandable that the web might not be responsive enough to give you the type of real time feedback you'd experience using a CD-ROM or other interactive applications. If it was possible for your browser to handle more complex tasks itself without asking the server every time it needed something done, you could have truly dynamic, responsive, and complex interactivity as part of the web.

After you've used the Web for a little while, you begin to notice that the every page consists of certain basic types of media. The main thing you see is text, with some simple formatting. The second thing you'll see is graphics, primarily in the form of GIF or JPEG images. If you don't recognize these formats, don't worry, its enough to know that they are special file formats which are often used because they can be viewed on a number of platforms and can be compressed so that they can be downloaded over the net as quickly as possible. Sometimes, you'll click on a link and a file will be downloaded to your computer, after which a separate application, called a helper application, will be launched which may play the downloaded file if its a sound or movie, or handle the file in some other special fashion.

Helper applications are so named because they help the browser out in dealing with file types the browser doesn't know how to handle. You'll quickly notice the difference between how elegantly the browser deals with files it does know how to handle (text, GIF files, etc.) and the ones it needs helper apps for. The files the browser knows how to handle are inline, meaning their contents are displayed right on the web page.

When you see a graphic on a Web page, its really a graphic file which the browser recognizes that it knows how to deal with and which is inlined on the page. Since the browser knows about these files, it can do cool things like starting to draw the file as it downloads rather than waiting until the whole file has been pulled down. Compare this to the clunky usage of helper applications, which need to have the whole file downloaded to your hard disk first, and then the application needs to be launched.

Depending on the configuration of your computer, a number of things might go wrong. You might not have the appropriate helper app, there might not be enough memory to load, etc. The general point is that its usually better to have a browser which has support for a type of file built in than have to use a helper app. Its because of this that some of the more ambitious vendors of browser software (i.e. Netscape) have been teaming up with the makers of some of the more popular helper applications to build the code for their helper apps directly into their browsers. A good example of this is Netscape's alliance with Adobe to built support for Adobe's Acrobat page description files into Netscape, rather than requiring Netscape users to have the Acrobat Reader configured as a helper application.

The problem here is that every day someone comes up with a great new application that can be used as a helper app. The browser makers are going to be extremely busy if they're going to be creating new versions of their products every time someone creates a new file format. The elegant solution would be if it were possible for the browser to automatically download the correct helper app when it encountered a file it didn't recognize and the ideal solution would be if the helper app was dynamically integrated into your browser after it had been downloaded.

Early in 1995, Sun announced a solution to these problems in the form of Hot Java, and made early versions of the software available on the net. The name Hot Java actually refers to the Web browser built using the Java language, although it is sometimes incorrectly used to describe any Web-related usage of Java. Hot Java takes advantage of the properties of the Java language to enable the usage of Java applets within the browser. Applets are sometimes described as client-side scripting. What this means is that the script, rather than being run on the remote server, is run on your (the client's) machine.

Applets are also often referred to as executable content. This means that it is content that knows the best way to present itself. If its a music file, it knows how to play itself. If its an animation, it knows how to draw itself, and what to do if its clicked on. Client-side scripting and executable content are both good ways of thinking about applications of Java applets, but that's not the whole story. The range of things made possible by Java is expanding every day as more and more people create new and amazing applications using this technology which is both extremely powerful, yet remarkably easy to use.

Hot Java and Java Browsers

As mentioned before, Hot Java properly describes Sun's Web browser which is written using the Java language and supports Hot Java applets. When most people refer to Hot Java, they actually mean Java applets, which are programs written in Java and are included in HTML pages. The Hot Java browser is similar to other popular browsers in the way it displays pages and allows you to navigate the Web. Most of your experience with Java applets will probably be when using Netscape 2.0. Netscape licensed Java from Sun and incorporated support for Java applets in Netscape 2.0, which went into public beta in October. Netscape intends to support Java across all the platforms that the Netscape Navigator will run on, and since Netscape owns the largest share of the browser market, their support for Java represents a significant endorsement of the technology.

In the next chapter, we will talk more about Hot Java and Netscape and show you where you can find the latest versions of both.

Java, the Language

Java is a simple, object-oriented language which has many elements in common with C and C++, but has removed or streamlined the areas where many programmers have had difficulty or have been the most frequent sources of bugs. Java also is a secure language, which means that it is possible to restrict the access of Java programs to parts of the system such as files and memory address, without limiting the capabilities of the language. This is one of the most important reasons why it is so well suited for use on the web.

Java has the benefits of an interpreted language, and the performance of compiled code. Users of rapid application development and scripting languages such as Visual Basic, Delphi, Perl, HyperCard, or AppleScript will be able to get started programming in Java in a very short time. Seasoned C and C++ programmers will find that they are able to easily translate their skills to this new language, and find that most of their code and algorithms are reusable as well. While Java is well suited to Internet related tasks, it is also a solid general purpose language which Sun intends to use in a variety of applications, and which will no doubt, through Sun's licensing efforts, turn up in a variety of products.

Where to Go from Here

This book is organized into four parts. Part I will cover how to get and install the Java Development Kit, run the Hot Java browser and Netscape, and visit any Java-enhanced site on the Web. Part II will cover the nuts and bolts of the Java language. Part III will cover building Applets in Hot Java, line by line. Part IV will summarize the reference material available on the SDK API Packages, the standard libraries for handling i/o, graphics, and net communications.

This book is written from the perspective that your goal is to ultimately be able to program your own Java applets. If you're new to Java and the Web, then you can focus on the chapters in Part I. If you're interested in learning how to program in Java, then you'll want to read through Part II. Experienced programmers will be able to skip many of the chapters in Part II, but may find that certain areas bear a closer look, especially objects, interfaces, packages, and threads. Part III is essential for understanding how to build Applets, and both novice and experienced programmers will want to study the chapters in this part. Part IV contains reference material which you'll need to have on hand when writing Applets and trying to understand example code.



Chapter 12: Threads

Depending on what types of applications and operating systems you've used in the past, you may or may not have a picture of what things like multi-tasking and concurrent processes mean and how they make your computer more responsive and interactive. Historically, when you used an operating systems such as the Macintosh and Windows, each application you .launched was a separate process. These application processes could switch between each other (for example, most Mac users are used to having several applications opened and switching between a word processor, a spreadsheet, and the Finder), as well as, to some extend, send messages between each other.

Within each application process, however, the applications had a single flow of control, which consisted of an event-loop, which is simply a loop, like the while loop we talked about in earlier chapters, which polls for events from the operating system, and depending on the event it receives, goes off a performs some task such as repaginating a document. When the application asks the operating system of the event, the operating system checks to see if any of the other applications need to perform any tasks, and gives them time to perform the tasks. The OS then gives the original application the event it requested and it is none the wiser to the fact that several other applications were just given execution time as well. As long as every application requests events from the OS on a regular and frequent basis, every application will in turn be given time to execute, and as a consequence, the system is able to provide the user with the perception that several applications are executing simultaneously.

Figure 12.1 Multiple Apps.


The problem with this is that if any of the applications need to perform a lengthy task, or a task where they don't feel comfortable in letting other applications run concurrent with, they can hog the system and not allow other applications to run simply by not requesting an event from the operating system. In some cases, certain operating system supplied services reinforce this type of situation. For example, on the Macintosh, while you are holding down a menu, other applications are unable to execute (this won't happen when you use menus within an applet in the Macintosh version of Java because it uses its own menu handling code rather than the operating system's) and anything else going on in the program stops, like animations that are playing. The reason all this happens is that the application really needs to be doing (at least) two things at the same time: dealing with events and user interaction (like the menu) and performing whatever other task or tasks that it needs to perform such as playing an animation.

Most versions of UNIX as well as new versions of the Windows and the Macintosh OS allow each application process to in turn own a number of lightweight processes or threads. An application that is written to take advantage of this feature can be though of as consisting of a number of sub-programs that execute at the same time. When the application needs to draw an animation running across the screen, it spawns a new process to handle the animation. The new process for the most part thinks of itself as the only program that is running, but in fact, many other processes are executing as well, such as threads to handle user interaction, or downloading files from the net.

The innovation of threads is not that they allow you to actually run two processes at the same time - most computers still only have one microprocessor to execute code with - but that they allow you to write the code for these processes as if they were in fact running simultaneously. Where Java comes into the picture is that it defines threads as an integral part of the language, and then goes on to put a number of features into the language that allow you to easily write Java programs which create multiple threads. In addition, Java handles the differences between the way threads are handled on various platforms for you, so that you don�t have to do anything special for your program to be able to create a thread on the Macintosh or under Solaris.

Understanding Threads

In Chapter 7 we talked about flow of control and the sequence in which Java executes your programs. Java also enables you to have multiple flows of control. Each flow of control can be thought of as a separate mini-program, referred to as a thread, that gets started by your application and runs in parallel to it. This thread has access to all the objects in your program and can call methods or access variables within them.

In a running Java program you will often have several threads running handling various activities such as tracking the mouse position, updating the position of on-screen graphical objects, or simply keeping time. The ability to have multiple threads running in a language is called concurrency and Java's threads are often referred to as lightweight threads, which means that they run in the same memory space. Because Java threads run in the same memory space, they can easily communicate between themselves, since an object in one thread can call a method in another thread without any overhead from the operating system.

Concurrency and Thread Execution

When we talk about concurrency, we don�t mean that threads actually run at the same time, but that the Java interpreter handles switching between threads for you to provide the perception that they are running concurrently. Lets image that we have two methods (runA() and runB()) which are each in running in a different thread and that the two threads were told to start execution at virtually the same time, although thread A was actually started first. Figure 12.2 shows how the two threads perceive that they are being executed by the processor.

Figure 12.2

As you can see, both threads are allowed to believe that they are running in an ideal computer which has two microprocessors. In reality, these two threads are running on a single processor system, and the flow of execution is shared between the two threads. Figure 12.3 shows how the flow of execution passes between the threads at certain points in the code: Figure 12.3 The output from these two threads would look like this:

A:Start
B:Start
A:1
B:1
A:2
B:2
A:Done
B:Done

Scheduling

As mentioned before, thread A was started first and as a consequence, it will be run first by the Java thread scheduler. While thread A is run, thread B waits for control of the processor to become available for it to execute. In multi-threaded programs, you think of the processor as a resource that becomes available and is lent to your thread for a certain period of time, When a thread relinquishes the processor, it is granted to the next waiting thread. With our two threads in this example, control of the processor alternates between after each one prints a line and then calls the yield() method, which causes control to be passed to the next waiting thread.

Priority

Each Java thread is assigned a priority, which affects how the Java thread scheduler chooses when to run a thread. The threads that we have looked at so far are of the same priority, so they share the processor on a first come, first serve basis. When a thread of a higher priority than any of the others comes into existence, it will get control of the processor, even if it needs to pre-empt a currently running thread to do so. In order for a thread of a lower priority to then gain control, the higher priority thread needs to go to sleep (using the sleep() method), or wait until notified (By calling the wait() method).

The life of a thread

Each thread is always in one of five states, as shown in Figure 12.4.

Figure 12.4

Threads move from one state to another via a variety of means. We indicate some of the most common methods for affecting a threads state in our thread state diagram.

Newborn

When a thread is first created but not yet run, it is in a special newborn state where it has had memory allocated for it, and private data has been initialized, but the thread has not yet been scheduled. At this point, a thread can be scheduled via the start() method, or killed via the stop() method. If it is scheduled, it moves to the runnable state.

Runnable

The runnable state means that a thread is ready to go and is awaiting control of the processor. Threads in the runnable state are said to be scheduled which means that they are in a queue while they wait their turns to be executed. When all threads in the queue are of equal priority, control is given in a first come first serve manner where the first thread in the queue is given control of the processor when the thread with control relinquishes it. Usually, the thread that relinquished control goes to the end of the queue, where it awaits its turn again. When a thread enters the queue, it is placed in front of any lower priority threads. If the thread at the start of the queue is of a higher priority than the currently executing thread, it will pre-empt it and kick it back to the end of the line. Usually, these types of high priority threads are created to perform some periodic task such as redrawing the screen. When they complete the task, they sleep for a predetermined period of time. When they wake, since they are of the highest priority, they go to the head of the line, and usually pre-empt the currently running task, in order to again perform their task, after which they go back to sleep, and the cycle repeats.

Most of the threads that you create will be of the same priority because you don�t necessarily want a bunch of high priority threads running around, nor do you want you threads to be disadvantaged and at the mercy of higher priority threads in order to get execution time. The system won't interrupt a thread to give control to another thread of equal priority, so in order for these threads to coexist, they must occasionally yield control to give other equal priority threads a chance to execute.

Running

Running means that the thread has control of the processor. Its code is currently being executed, and it owns the processor until it gets pre-empted by a higher priority thread, or it relinquishes control. A thread relinquishes control usually in one of three ways:
1. It can yield control which means that it wont regain control until it is the next equal priority thread in line to regain control of the processor.
2. It can go to sleep for a certain period of time, which means that it wont be entered into the queue to be run again until after a certain period of time.
3. It can wait for some event to notify it that it should be scheduled to run again.

Blocked

When a thread is blocked, it means that it is being prevented from entering the Not Runnable state (and subsequently the Running state). A blocked thread is essentially waiting for some event in order for it to re-enter the scheduling queue. This event is dependent on what caused the thread to block in the first place. A sleeping thread is blocked until a certain amount of time has elapsed, and suspended thread is blocked until it is requested to resume, and a waiting thread is blocked until it is notified of a change in the condition of another thread.

Dead

When a thread has completed execution, either by running its course, or being stopped by another thread, the thread is killed.

Synchronization

Since threads in Java are running in the same memory space, they can share access to variables and methods in objects. In the case of an object being shared between two threads, where, for example, one thread stores data into the shared object and the other thread reads that data, there can be problems of synchronization if the first thread hasn't finished storing the data before the second one goes to read it. There are a number of situations where threads will need to make use of shared objects and resources, and without some synchronization facilities built into the language, things could quickly get out of hand as multiple threads try to write to the same files, read characters from the same input devices, or try to draw to a window that isn't visible yet..

Exclusion and objects

The idea behind synchronization is to prevent two or more threads from trying to access the same resource. In an object oriented language like Java, resources are represented in the form of objects, and Java's synchronization features serve to prevent multiple threads from performing conflicting tasks on the same object. How does Java know what is a conflicting task? The answer is that it doesn't, and when a class is designed with threads in mind, the class designer decides which methods should not be allowed to execute concurrently. This is done by indicating that a method is synchronized and a class may have several synchronized methods.

When a class with synchronized methods is instantiated, the new object is given a monitor. In order for a thread to call a synchronized message in an object, it acquire the monitor of that object. If it is able to acquire the monitor, it enters the synchronized method, and while it owns the monitor, no other thread can call a synchronized method in that object. If a thread calls a synchronized method in an object and that object's monitor is owned by another thread, the calling thread is blocked until the other thread relinquishes the monitor. When the original thread exits the synchronized method where it acquired the monitor, ownership of the monitor is transferred to the blocked thread which is now able to enter the method it was blocked on. By providing this mechanism for specific which methods can run concurrently, Java allows you to prevent situations where to threads are fight over the same resource. We'll see how this works in practice in later sections.

Using Threads

Threads in Java are implemented in the form of objects which contain a method called run(). When the t thread is started, the run() methods is executed in its own thread. There are two ways of providing the run() method for a thread. The first way is to subclass the Thread class and override its run() method. The second way is use the Runnable interface to add a run method to an existing class and then attach an instantiated thread to it. We'll take a look at both approaches in this section.

In order to better understand threads in action, we should step through a simple Java application that uses a subclass of Thread in order to perform a repetitive action. First, lets take a look at the program listing, then we'll talk about what the various elements of it mean:

class CountThread extends Thread {
	int maxcount;

	CountThread(int maxcount) {
		this.maxcount = maxcount;
	}

	public void run() {
		for(int count = 1; count < maxcount; count++) {
			System.out.println("The count is " + count + ".");
			try {
				sleep(10);
			} catch (InterruptedException e) {
				return;
			}
		}
	}
}

class CountApp {
	public static void main (String args[]) {
		CountThread theCounter = new CountThread(10);
		theCounter.start();
		while (theCounter.isAlive()) {
			System.out.println("Counting...");
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				return;
			}
		}
	}
}

If we run this application, the output will look like this:
$ java CountApp
Counting...
The count is 1.
Counting...
The count is 2.
Counting...
The count is 3.
Counting...
The count is 4.
Counting...
The count is 5.
Counting...
The count is 6.
Counting...
The count is 7.
Counting...
The count is 8.
Counting...
The count is 9.
Counting...


Lets now take a closer look at the example and understand what�s happening. The code for the CountApp looks similar to other applications we've looked at before. There are several points of interest, however. First of all, we perform the following statements which create our Thread object and call the start() method in it:
		CountThread theCounter = new CountThread(10);
		theCounter.start();


These commands instantiate a CountThread object. We defined the CountThread class in our listing, and we're responsible for the contructor method, which takes an integer parameter to use as the value to count up to. After this thread has been instantiated, it is in the newborn state we discussed earlier the chapter. New threads are assigned the same priority as the threads that create them (remember, all Java code is running in a thread, either one created by you or by the system).

The only other method we declare within CountThread is the run() method, so the start() method that we see being called in the above statements must be inherited from Thread. The start() method causes a newborn Thread to be scheduled, which means that it enters the runnable state and is waiting to gain control of the processor. The start() method immediately returns to the caller, which in this case is our main() method.

At this point, we have two threads in our program. The first is the one that we are currently running, which is the system-created primary thread, and the second is the CountThread we just created and which is waiting for an opportunity to run. In the next statement of our main() method, we enter and repeatedly execute the following loop:

	public static void main (String args[]) {
		CountThread theCounter = new CountThread(10);
		theCounter.start();
		while (theCounter.isAlive()) {
			System.out.println("Counting...");
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				return;
			}
		}
	}
}

The highlighted code is executed as long the isAlive() method in theCounter returns true. The isAlive() method is defined in java.lang.Thread, and returns true as long as a thread's run() method is still executing. When that run() method completes, isAlive() will return false. Our loop prints out "Counting..." as long as the CountThread is running.

There are two other interesting things in this loop that you might be wondering about. First of all, we are calling Thread.sleep(10) once every time through the loop, and secondly, we have wrapped the call with an exception handler which catches an InterruptedException. Lets examine these one at a time.

The sleep() method in the Thread class causes the currently running thread go to sleep by blocking for a certain period of time. This has the effect of causing the next runnable thread to gain control of the processor. Since the two threads are of equal priority, Java will not pre-empt between them. Because of this, we need to ensure that the running thread yields time to other threads. This can be done by calling the static yield() or sleep() methods in the Thread class.

The yield() method simply passes control to the next waiting thread. The sleep() method is called with the number of milliseconds that you want this thread to sleep before being scheduled to resume control. In terms of our previous illustration of the states of a thread, the thread is blocked for a certain period of time before it re-nets the runnable queue. When calling sleep() from outside a class that is a subclass of Thread(), you need to need to call it using the class name like you would any static method.

The other interesting thing about the loop in our example main() method is the fact that it is wrapped in an exception handler which catches the InterruptedException which the sleep() method declares that it can throw. Since InterruptedException is one of the exceptions which we need to catch (from our list of exceptions in the previous chapter), we need to set up an exception handler to deal with it. You will see this type of exception handler used in many of our examples. If we had neglected to set up this handler, we would have gotten an error message from the compiler warning us of the potential for uncaught exceptions.

Inside the Thread Class

Threads in Java are created and managed through use of the Thread class. This class provides a number of methods for controlling the state, priority, and scheduling of a thread. Lets take a closer look at the Thread class and the methods we used in our last example. Threads have several contructor methods. The most common ones you will use are the following:

public Thread()

public Thread(Runnable target)

You will find other versions of the constructor in the class reference, but these are the basic one that you will use in most cases. When called with no parameters, the constuctor initializes the new thread object and places it in the newborn stated. You will use this constructor when you are working with classes that you have subclasses from Thread. The second version of the constructor is used when you want to make an existing object the target of a thread. We will talk more about what this means in the next section, but essentially it means that the code for the thread to execute is found in a separate object (the target) than the thread. This constructor is mostly used when you are working with actual instances of Thread and not subclasses of it.

Thread objects can be started and stopped with the start() and stop() methods:

public synchronized void start()

public final void stop()
Starting a thread sends it to the Runnable state, while stopping a thread sends it to the Dead state. These methods are usually called from outside the thread that you want to start or stop. In order to ensure that other threads get time, you can yield to other threads or put the current thread to sleep for a certain period of time. Yielding a thread is done through the yield() method and putting a thread is done through the sleep() method:
public static void yield()

public static void sleep(long millis) throws InterruptedException
Yielding control means that the next scheduled thread of equal priority gets control of the processor, while the yielding thread re-enters the end of the runnable queue. If there are no threads of equal priority, the thread essentially goes to the head of the runnable queue and is once again given control of the processor. The net effect is that if there are not other threads of equal priority, yielding has no effect.

The sleep method puts the currently running thread into the blocked stated until the time indicated expires. After that point, the thread re-enters the runnable queue. The sleep() method can throw an InterruptedException so that you must catch it with an exception handler, especially if calling sleep() from within a run() method, which does not declare that it throws any exceptions, and as a consequence is prevented from throwing any exception except runtime exceptions. Most sleep() methods will be wrapped in an exception handler like this:

	try {
		sleep(10);
	} catch (InterruptedException e) {
		return;
	}

setPriority
get priority

setDaemon
isDaemon

The run() method In order for a thread to run, it must have a run() method either within it or its target. The run() method, which is defined as follows:

public void run()

At first glance, this seems to be a very simple method to implement. The problem lies with the face that the method does not declare that it throws any exceptions. This is a problem because exception throws are frequent in threads, especially when created by applets for HotJava. As a consequence, you will often need to include an exception handler within your run method in order to catch any exceptions that might be thrown by a thread becoming interrupted, for example by the use switching to a different page. Here is an example of an exception handler that will allow your thread to exit gracefully regardless of the exception thrown.

	try {
		sleep(10);
	} catch (Exception e) {
		return;
	}

As you can see, our exception handler in this example will catch any Exception subclass, which is going to be the case for the majority of exceptions throwable. In most cases, you want to avoid these blanket exceptions, especially when debugging your program, but in your final code you may want to consider such steps to make sure that any threads don�t cause the program to halt.

Subclassing Thread

One method of making use of threads within a program is to create your own subclass of Thread which is designed to perform a specific task in its own thread. When subclassing the Thread class, you need to have a run() method within your new subclass. For example, here�s the CountThread class we used in some of our earlier examples:

class CountThread extends Thread {
	int maxcount;

	CountThread(int maxcount) {
		this.maxcount = maxcount;
	}

	public void run() {
		for(int count = 1; count < maxcount; count++) {
			System.out.println("The count is " + count + ".");
			try {
				sleep(10);
			} catch (InterruptedException e) {
				return;
			}
		}
	}
}

This class demonstrates the classic design of a Thread subclass. The class is defined as extending Thread and it had its own constructor and run() method. The constructor is not strictly required, but its good practice to have it. The run() method is present as is required, and, like most run() methods, this one consists mainly of a loop that executes a certain amount of times before the method exits and the thread subsequently dies. Also, the run() method correctly wraps its call of the sleep() method in an exception handler. Most threads that you create are going to be variations of this basic implementation of a thread subclass, although they will very likely be much more complicated.

Using the Runnable Interface

A second way to uses threads is to make use of the Runnable interface. When using this approach, you will have one of your existing classes be defined as implementing the Runnable interface. When you later go to create a new thread, you will define an object which was instantiated from this "runnable" class as the target of your thread, meaning that the thread will look for the code for the run() method within your object's class instead of inside the thread's class. The Runnable interface is defined as follows:

public interface Runnable {
	public abstract void run();
}

Implementing this interface in one of your classes means that you need to have a run() method within your class. It is important that this method be defined as follows: public void run()

class CircleThread extends Thread {
	int numsides;
	double radius;

	CircleThread(int numsides, double radius) {
		this.numsides = numsides;
		this.radius = radius;
	}

	public void run() {
	double incAngle = (2 * Math.PI) / numsides;
		for(int sides = 0; sides < numsides; sides++) {
			// Point on circle
			double dx = radius * Math.cos(sides * incAngle);
			double dy = radius * Math.sin(sides * incAngle);
			// print it
			System.out.println("x:" + dx + " y:" + dy);
			yield();
		}
	}
}

Implementing Synchronization

Earlier in this chapter we saw that every object which has synchronized methods is given a monitor. A synchronized method is a method that can only be entered if the thread in which the method is called is able to acquire the monitor of the object, otherwise the thread in which the synchronized method is called will be blocked until the monitor is relinquished by another thread. A monitor can only be acquired by one thread at a time, and when another thread needs to acquire the monitor, that thread must wait for the current thread to relinquish the monitor. By marking a method or methods of an object as synchronized, you can prevent different threads from calling these methods at the same time.

Synchronized methods and blocks

Methods and blocks are marked as synchronized by using the synchronized keyword modifier. The following lines of code show a method that is marked as synchronized:

	synchronized int getPipedInt() {
		// synchronized code
		...
	}

When a code running within a thread attempts to enter a synchronized method of an object, it will be blocked until it can acquire the object's monitor. If the monitor is in use by another thread, the method cannot be entered until the other thread relinquishes the monitor. When this happens the waiting thread is able to resume execution and enter the method.

A synchronize block works a little differently. In this case, the synchronized keyword is used to mark a block of code that can't be entered until the monitor of the specified object can be acquired. This object (or class) can be entirely different that object whose method contains the synchronized block we are trying to enter. Once we acquire the monitor and enter the block, other threads will be unable to enter synchronized methods of the specified object until we exit the synchronized block and relinquish the specified object's monitor. Here's an example of it's use:

	synchronized (lock_object) {
		// synchronized code
		...
	}

wait() and notify()

Once inside a synchronized method, it is sometimes useful to temporarily relinquish the monitor and allow other threads to acquire the monitor if they need to. This is done by calling the wait() method. This method can be called to wait indefinitely or until a set amount of time has elapsed. A waiting thread can reacquire the monitor if another thread calls the notify() method of the object where the wait() method was originally called. This causes the original thread to resume execution at the point where it called wait().

 

Figure 12.5 lists the methods for causing a thread to wait, and notifying waiting threads to resume. These methods must be called from synchronized methods:

Object.wait 
public final void wait(long timeout) throws InterruptedException

Causes a thread to wait for the specified number of milliseconds or until notified.

Object.wait 
public final void wait(long timeout,
                         int nanos) throws InterruptedException

Causes a thread to wait for the specified number of milliseconds and nanoseconds or until notified.

Object.wait 
public final void wait() throws InterruptedException

Causes a thread to wait indefinitely or until notified.

Object.notify 
public final void notify()

Notifies a thread which is waiting within a synchronized method of this object.

Object.notifyAll 
public final void notifyAll()

Notifies all threads which are waiting within a synchronized method of this object. Figure 12.5 Methods for causing a thread to wait.

Threadsafe Variables

Threadsafe variables are variables the are marked by the programmer to tell the compiler that they are never modified by more than one thread. This allows the compiler to perform optimizations such as using a processor register to contain the variable's value. If you mark a variable as threadsafe, it should only be because you are certain that it will never be accessed from another thread, otherwise situations will arise where a thread will get an incorrect value when accessing the variable because another thread was working with a copy of the variable that had been cached in a processor register.



 

  Cover

ISBN 0-471-14859-8
498 pages
March, 1996

 

Wiley Computer Publishing
Timely. Practical. Reliable.