Statistics
13
Views
0
Downloads
0
Donations
Uploader

高宏飞

Shared on 2025-12-20
Support
Share

AuthorScott Oaks, Henry Wong

Threads are essential to Java programming, but learning to use them effectively is a nontrivial task. This new edition of the classic Java Threads shows you how to take full advantage of Java's threading facilities and brings you up-to-date with the watershed changes in Java 2 Standard Edition version 5.0 (J2SE 5.0). It provides a thorough, step-by-step approach to threads programming.Java's threading system is simple relative to other threading systems. In earlier versions of Java, this simplicity came with tradeoffs: some of the advanced features in other threading systems were not available in Java. J2SE 5.0 changes all that: it provides a large number of new thread-related classes that make the task of writing multithreaded programs that much easier.You'll learn where to use threads to increase efficiency, how to use them effectively, and how to avoid common mistakes. This book discusses problems like deadlock, race conditions, and starvation in detail, helping you to write code without hidden bugs.Java Threads, Third Edition, has been thoroughly expanded and revised. It incorporates the concurrency utilities from java.util.concurrent throughout. New chapters cover thread performance, using threads with Swing, threads and Collection classes, thread pools, and threads and I/O (traditional, new, and interrupted). Developers who cannot yet deploy J2SE 5.0 can use thread utilities provided in the Appendix to achieve similar functionality with earlier versions of Java.Topics include: Lock starvation and deadlock detection Atomic classes and minimal synchronization (J2SE 5.0) Interaction of Java threads with Swing, I/O, and Collection classes Programmatically controlled locks and condition variables (J2SE 5.0) Thread performance and security Thread pools (J2SE 5.0) Thread groups Platform-specific thread scheduling Task schedulers (J2SE 5.0) Parallelizing loops for multiprocessor machines In short, this new edition of Java Threads covers everything you need to know ab

Tags
No tags
ISBN: 0596007825
Publisher: O’Reilly Media
Publish Year: 2004
Language: 英文
Pages: 362
File Format: PDF
File Size: 2.1 MB
Support Statistics
¥.00 · 0times
Text Preview (First 20 pages)
Registered users can read the full content for free

Register as a Gaohf Library member to read the complete e-book online for free and enjoy a better reading experience.

(This page has no text content)
Java Threads, 3rd Edition Scott Oaks Henry Wong Beijing • Cambridge • Farnham • Köln • Sebastopol • Tokyo 2
Special Upgrade Offer If you purchased this ebook directly from oreilly.com, you have the following benefits: DRM-free ebooks — use your ebooks across devices without restrictions or limitations Multiple formats — use on your laptop, tablet, or phone Lifetime access, with free updates Dropbox syncing — your files, anywhere If you purchased this ebook from another retailer, you can upgrade your ebook to take advantage of all these benefits for just $4.99. Click here to access your ebook upgrade. Please note that upgrade offers are not available from sample content. 3
A Note Regarding Supplemental Files Supplemental files and examples for this book can be found at http://examples.oreilly.com/9780596007829/. Please use a standard desktop web browser to access these files, as they may not be accessible from all ereader devices. All code files or examples referenced in the book will be available online. For physical books that ship with an accompanying disc, whenever possible, we’ve posted all CD/DVD content. Note that while we provide as much of the media content as we are able via free download, we are sometimes limited by licensing restrictions. Please direct any questions or concerns to booktech@oreilly.com. 4
Preface When Sun Microsystems released the alpha version of Java© in the winter of 1995, developers all over the world took notice. There were many features of Java that attracted these developers, not the least of which were the set of buzzwords Sun used to promote the language. Java was, among other things, robust, safe, architecture- neutral, portable, object-oriented, simple, and multithreaded. For many developers, these last two buzzwords seemed contradictory: how could a language that is multithreaded be simple? It turns out that Java’s threading system is simple, at least relative to other threading systems. This simplicity makes Java’s threading system easy to learn so that even developers who are unfamiliar with threads can pick up the basics of thread programming with relative ease. In early versions of Java, this simplicity came with tradeoffs; some of the advanced features that are found in other threading systems were not available in Java. Java 2 Standard Edition Version 5.0 (J2SE 5.0) changes all of that; it provides a large number of new thread-related classes that make the task of writing multithreaded programs that much easier. Still, programming with threads remains a complex task. This book shows you how to use the threading tools in Java to perform the basic tasks of threaded programming and how to extend them to perform more advanced tasks for more complex programs. Who Should Read This Book? This book is intended for programmers of all levels who need to learn to use threads within Java programs. This includes developers who have previously used Java and written threaded programs; J2SE 5.0 includes a wealth of new thread-related classes and features. Therefore, even if you’ve written a threaded program in Java, this book can help you to exploit new features of Java to write even more effective programs. The first few chapters of the book deal with the issues of threaded programming in Java, starting at a basic level; no assumption is made that the developer has had any experience in threaded programming. As the chapters progress, the material becomes more advanced, in terms of both the information presented and the experience of the developer that the material assumes. For developers who are new to threaded programming, this sequence should provide a natural progression of the topic. This book is ideally suited to developers targeting the second wave of Java programs — more complex programs that fully exploit the power of Java’s threading system. We make the assumption that readers of the book are familiar with Java’s syntax and features. In a few areas, we present complex programs that depend on knowledge of other Java features: AWT, Swing, NIO, and so on. However, the basic principles we present should be understandable by anyone with a basic knowledge of Java. We’ve found that books that deal with these other APIs tend to give short shrift to how multiple threads can fully utilize these features of Java (though doubtless the reverse is true; we make no attempt to explain nonthread-related Java APIs). Though the material presented in this book does not assume any prior knowledge of threads, it does assume that the reader has knowledge of other areas of the Java API and can write simple Java programs. Versions Used in This Book Writing a book on Java in the age of Internet time is hard — the sand on which we’re standing is constantly shifting. But we’ve drawn a line in that sand, and the line we’ve drawn is at the Java 2 Standard Edition (J2SE) Version 5.0 from Sun Microsystems. This software was previously known as J2SE Version 1.5. It’s likely that versions of Java that postdate this version will contain some changes to the threading system not discussed in this edition of the book. We will also point out the differences between J2SE 5.0 and previous versions of Java as we go so that developers using earlier releases of Java will also be able to use this book. Most of the new threading features in J2SE 5.0 are available (with different APIs) from third-parties for earlier versions of Java (including classes we developed in earlier editions of this book). Therefore, even if you’re not using J2SE 5.0, you’ll get full benefit from the topics covered in this book. What’s New in This Edition? This edition includes information about J2SE 5.0. One of the most significant changes in J2SE 5.0 is the inclusion of Java Specification Request (JSR) 166, often referred to as the "concurrency utilities.” JSR-166 specifies a number of thread-related enhancements to existing APIs as well as providing a large package of new APIs. These new APIs include: 5
Atomic variables A set of classes that provide threadsafe operations without synchronization Explicit locks Synchronization locks that can be acquired and released programmatically Condition variables Variables that can be the subject of a targeted notification when certain conditions exist Queues Collection classes that are thread-aware Synchronization primitives New classes that perform complex types of synchronization Thread pools Classes that can manage a pool of threads to run certain tasks Thread schedulers Classes that can execute tasks at a particular point in time We’ve fully integrated the new features of J2SE 5.0 throughout the text of this edition. The new features can be split into three categories: New implementations of existing features The Java language has always had the capability to perform data synchronization and thread notification. However, implementation of these features was somewhat limited; you could, for example, synchronize blocks of code or entire methods but synchronizing across methods and classes required extra programming. In J2SE 5.0, explicit locks and condition variables allow you more flexibility when using these features. These new implementations do not introduce new concepts for a developer. A developer who wants to write a threadsafe program must ensure that her data is correctly synchronized, whether she uses J2SE 5.0’s explicit locks or the more basic synchronized keyword. Therefore, both are presented together when we talk about data synchronization. The same is true of condition variables, which provide thread notification and are discussed alongside Java’s wait( ) and notify( ) methods, and of queues, which are discussed along with Java’s other collection classes. Important thread utilities At some point in time, virtually all developers who write threaded programs will need to use basic thread utilities such as a pool or a scheduler; many of them will also need to use advanced synchronization primitives. A recognition of this fact is one thing that drove JSR-166 — it was certainly possible in previous versions of Java to develop your own thread pools and schedulers. But given the importance of threading in the Java platform, adding these basic utilities greatly increases programmer productivity. Minimal synchronization utilities Java’s new atomic classes provide a means by which developers can, when necessary, write applications that avoid synchronization. This can lead to programs that are highly concurrent. If you’ve read previous editions of this book, the concepts presented in the first two categories will be familiar. In previous editions, we developed our own data synchronization classes, thread pools, and so on. In those editions, we explained in detail how our implementations worked and then used them in several examples. In this edition, we focus solely on how to use these classes effectively. The information that falls into the third category is completely new to this edition. The classes that perform minimal synchronization require new support from the virtual machine itself and could not be developed independent of those changes. Organization of This Book Here’s an outline of the book, which includes 15 chapters and 1 appendix: Chapter 1 This chapter forms a basic introduction to the topic of threads: why they are useful and our approach to discussing them. Chapter 2 This chapter shows you how to create threads and runnable objects while explaining the basic principles of 6
how threads work. Chapter 3 This chapter discusses the basic level at which threads share data safely — coordinating which thread is allowed to access data at any time. Sharing data between threads is the underlying topic of our next four chapters. Chapter 4 This chapter discusses the basic technique threads use to communicate with each other when they have changed data. This allows threads to respond to data changes instead of polling for such changes. Chapter 5 This chapter discusses classes and programming methods that achieve data safety while using a minimal amount of synchronization. Chapter 6 In this chapter, we complete our examination of data sharing and synchronization with an examination of deadlock, starvation, and miscellaneous locking classes. Chapter 7 Swing classes are not threadsafe. This chapter discusses how multithreaded programs can take full advantage of Swing. Chapter 8 Java collection classes are written for a variety of circumstances. Some are threadsafe and some are not, and J2SE 5.0 introduces new collection classes for use specifically with thread utilities. We sort all that out in this chapter. Chapter 9 Scheduling is the process whereby a single CPU selects a thread to run. Thread scheduling is more a property of an operating system (OS) than a Java program, and this chapter discusses the relationship between the virtual machine and the OS in this area. Chapter 10 This chapter discusses thread pools — a collection of threads that can be used to run arbitrary tasks. We use the thread pool implementation of J2SE 5.0 for discussion of the general principles of using thread pools. Chapter 11 Task schedulers execute a task one or more times at some point in the future. This set of classes includes timers (Java has had timer classes since JDK 1.3) and a general task scheduler available in J2SE 5.0. Chapter 12 Dealing with I/O is one of the primary reasons why developers use threads in Java. In this chapter, we use all of Java’s threading features to show you how to handle I/O effectively in multithreaded programs. Chapter 13 In this chapter, we complete our examination of thread-related features of Java by examining thread security, thread groups, thread stacks, and other topics. Chapter 14 Performance of thread-related features — and particularly synchronization constructs — is key to writing multithreaded programs. In this chapter, we test various low-level programming features and explore some truths and myths about thread performance. Chapter 15 In this chapter, we show a process for exploiting the power of multiprocessor machines to calculate CPU- intensive loops in parallel. Appendix A J2SE 5.0 introduces a number of thread-related classes. Many of these classes are similar to classes developed in previous editions of this book; we list those classes in this appendix as an aid to developers who cannot yet upgrade to J2SE 5.0. Conventions Used in This Book The following typographical conventions are used in this book: 7
Italic Indicates URLs and filenames, and is used to introduce new terms. Sometimes we explain thread features using a question-and-answer format. Questions posed by the reader are rendered in italic. Constant width Indicates code examples, methods, variables, parameters, and keywords within the text. Constant width bold Indicates user input, such as commands that you type on the command line. Code Examples All examples presented in the book are complete, running applications. However, many of the program listings are shortened because of space and readability considerations. The full examples may be retrieved online from http://www.oreilly.com/catalog/jthreads3. This book is here to help you get your job done. In general, you may use the code in this book in your programs and documentation. You do not need to contact us for permission unless you’re reproducing a significant portion of the code. For example, writing a program that uses several chunks of code from this book does not require permission. Selling or distributing a CD-ROM of examples from O’Reilly books does require permission. Answering a question by citing this book and quoting example code does not require permission. Incorporating a significant amount of example code from this book into your product’s documentation does require permission. We appreciate, but do not require, attribution. An attribution usually includes the title, author, publisher, and ISBN. For example: "Java Threads, Third Edition, by Scott Oaks and Henry Wong. Copyright 2004 O’Reilly Media, 0-596-00782-5.” If you feel your use of code examples falls outside fair use or the permission given above, feel free to contact us at permissions@oreilly.com. How to Contact Us Please address comments and questions concerning this book to the publisher: O’Reilly Media, Inc. 1005 Gravenstein Highway North Sebastopol, CA 95472 (800) 998-9938 (in the United States or Canada) (707) 829-0515 (international or local) (707) 829-0104 (fax) O’Reilly maintains a web page for this book, where we list errata, examples, and any additional information. You can access this page at: http://www.oreilly.com/catalog/jthreads3 To comment or ask technical questions about this book, send email to: bookquestions@oreilly.com For more information about O’Reilly books, conferences, Resource Centers, and the O’Reilly Network, see our web site at: http://www.oreilly.com Safari Enabled When you see the Safari® Enabled icon on the back cover of your favorite technology book, that means the book is available online through the O’Reilly Network Safari Bookshelf. Safari offers a solution that’s better than e-books. It’s a virtual library that lets you easily search thousands of 8
top technology books, cut and paste code samples, download chapters, and find quick answers when you need the most accurate, current information. Try it for free at http://safari.oreilly.com. Acknowledgments As readers of prefaces are well aware, writing a book is never an effort undertaken solely by the authors who get all the credit on the cover. We are deeply indebted to the following people for their help and encouragement: Michael Loukides, who believed us when we said that this was an important topic and who shepherded us through the creative process; David Flanagan, for valuable feedback on the drafts; Deb Cameron, for editing sometimes rambling text into coherency; Hong Zhang, for helping us with Windows threading issues; and Reynold Jabbour, Wendy Talmont, Steve Wilson, and Tim Cramer for supporting us in our work over the past six years. Mostly, we must thank our respective families. To James, who gave Scott the support and encouragement necessary to see this book through (and to cope with his continual state of distraction), and to Nini, who knew to leave Henry alone for the ten percent of the time when he was creative, and encouraged him the rest of the time — thank you for everything! Finally, we must thank the many readers of the earlier editions of this book who sent us invaluable feedback. We have tried our best to answer every concern that they have raised. Keep those cards and letters coming! 9
Chapter 1. Introduction to Threads This is a book about using threads in the Java programming language and the Java virtual machine. The topic of threads is very important in Java — so important that many features of the threading system are built into the Java language itself while other features of the threading system are required by the Java virtual machine. Threading is an integral part of using Java. The concept of threads is not a new one: for some time, many operating systems have had libraries that provide the C programmer a mechanism to create threads. Other languages, such as Ada, have support for threads embedded into the language, much as support for threads is built into the Java language. Nonetheless, until Java came along, the topic of threads was usually considered a peripheral programming topic, one that was only needed in special programming cases. With Java, things are different: it is impossible to write any but the simplest Java program without introducing the topic of threads. And the popularity of Java ensures that many developers, who might never have considered learning about threading possibilities in a language such as C or C++, need to become fluent in threaded programming. Futhermore, the Java platform has matured throughout the years. In Java 2 Standard Edition Version 5.0 (J2SE 5.0), the classes available for thread-related programming rival many professional threading packages, mitigating the need to use any commercial library (as was somewhat common in previous releases of Java). So Java developers not only need to become knowledgeable in threaded programming to write basic applications but will want to learn the complete, rich set of classes available for writing complex, commercial-grade applications. Java Terms Let’s start by defining some terms used throughout this book. Many Java-related terms are used inconsistently in various sources; we endeavor to be consistent in our usage of these terms throughout the book. Java First, is the term Java itself. As you know, Java started out as a programming language, and many people today still think of Java as being simply a programming language. But Java is much more than just a programming language: it’s also an API specification and a virtual machine specification. So when we say Java, we mean the entire Java platform: the programming language, its APIs, and a virtual machine specification that, taken together, define an entire programming and runtime environment. Often when we say Java, it’s clear from the context that we’re talking specifically about the programming language, or parts of the Java API, or the virtual machine. The point to remember is that the threading features we discuss in this book derive their properties from all the components of the Java platform taken as a whole. While it’s possible to take the Java programming language, directly compile it into assembly code, and run it outside of the virtual machine, such an executable may not necessarily behave the same as the programs we describe in this book. Virtual machine, interpreters, and browsers The Java virtual machine is the code that actually runs a Java program. Its purpose is to interpret the intermediate bytecodes that Java programs are compiled into; the virtual machine is sometimes called the Java interpreter. However, modern virtual machines usually compile the majority of the code they run into native instructions as the program is executing; the result is that the virtual machine does little actual interpretation of code. Browsers such as Mozilla, Netscape Navigator, Opera, and Internet Explorer all have the capability to run certain Java programs (applets). Historically, these browsers had an embedded virtual machine; today, the standard Java virtual machine runs as a plug-in to these browsers. That means that the threading details of Java-capable browsers are essentially identical to those of a standard Java virtual machine. The one significant area of difference lies in some of the default thread security settings for browsers (see Chapter 13). Virtual machine implementations are available from many different vendors and for many different operating systems. For the most part, virtual machines are indistinguishable — at least in theory. However, because threads are tied to the operating system on which they run, platform-specific differences in thread behavior do crop up. These differences are important in relatively few circumstances, and we discuss them in Chapter 9. Programs, applications, applets, and other code This leads us to the terms that we use for things written in the Java language. Like traditional programming models, Java supports the idea of a standalone application, which in the case of Java is run from the command 10
line (or through a desktop chooser or icon). The popularity of Java has led to the creation of many new types of Java-enabled containers that run pieces of Java code called components . Web server containers allow you to write components (servlets and Java Server Page or JSP classes) that run inside the web server. Java- enabled browsers allow you to write applets: classes that run inside the Java plug-in. Java 2 Enterprise Edition (J2EE) application servers execute Enterprise Java Beans (EJBs), servlets, JSPs, and so on. Even databases now provide the ability to use server-side Java components. As far as Java threads are concerned, the distinction between the different types of containers is usually only the location of the objects to be executed. Certain containers place restrictions on threaded operations (which we discuss in Chapter 13), and in that case, we discuss specific components. Apart from the rare case where we specifically mention a type of component, we just use the term program since the concepts discussed apply to all of the Java code you might write. Concurrency and threads J2SE 5.0 includes a package known as the "concurrency utilities,” or JSR-166. Concurrency is a broad term. It includes the ability to perform multiple tasks at the same time; we generally refer to that ability as parallelism. As we’ll see throughout this book, threaded programming is about more than parallelism: it’s also about simpler program design and coping with certain implementation features of the Java platform. The features of Java (including those of JSR-166) help us with these tasks as well. Concurrency also includes the ability to access data at the same time in two or more threads. These are issues of data synchronization, which is the term we use when discussing those aspects of concurrency. Java Versions, Tools, and Code We also need to be concerned with specific versions of Java itself. This is an artifact of the popularity of Java, which has led to several major enhancements in the platform. Each version supplements the thread-related classes available to developers, allowing them to work with new features or no longer to rely on externally developed classes. We focus in this book on J2SE 5.0.[1] This version contains a wealth of new thread-related classes and features. These classes greatly simplify much of the work in developing threaded applications since they provide basic implementations of common threading paradigms. The new features of J2SE 5.0 are integrated throughout the Java platform; we’ve integrated the new features throughout our discussion as well. When we discuss J2SE 5.0, we clearly identify the new features as such. If you’re unable to use those features because you cannot yet upgrade the version of Java you’re using, you’ll find similar functionality to almost all J2SE 5.0 features in the classes provided in the Appendix A, which contains implementations of common threading utilities that were developed in previous versions of this book; these utilities use an earlier version of Java. ALL THINGS JUST KEEP GETTING BETTER It’s interesting to note the differences between this edition of Java Threads and the previous editions. In earlier editions of this book, we developed classes to perform explicit locks, condition variables, thread pooling, task scheduling, and so on. All that functionality and more is now included in the core J2SE 5.0 platform. In Chapter 14, we look at thread performance; the performance of basic thread-related operations (and especially uncontended lock acquisition) has greatly improved since we first looked at this in JDK 1.1. And in order to obtain meaningful, long-running results for our parallelism tests in Chapter 15, we had to increase the number of calculations by a significant factor. About the Examples Full code to run all the examples in this book can be downloaded from http://www.oreilly.com/catalog/jthreads3. Code is organized by packages in terms of chapter number and example number. Within a chapter, certain classes apply to all examples and are in the chapter-related package (e.g., package javathreads.examples.ch02). The remaining classes are in an example-specific package (e.g., package javathreads.examples.ch02.example1). Package names are shown within the text for all classes. Examples within a chapter (and often between chapters) tend to be iterative, each one building on the classes of previous examples. Within the text, we use ellipses in code samples to indicate that the code is unchanged from previous examples. For instance, consider this partial example from Chapter 2: package javathreads.examples.ch02.example2; ... public class SwingTypeTester extends JFrame { ... private JButton stopButton; ... 11
private void initComponents( ) { ... stopButton = new JButton( ); The package name tells us that this is the second example in Chapter 2. Following the ellipses, we see that there is a new instance variable (stopButton) and some new code added to the initComponents() method. For reference purposes, we list the examples and their main class at the end of each chapter. Compiling and Running the Examples The code examples are written to be compiled and run on J2SE 5.0. We use several new classes of J2SE 5.0 throughout the examples and occasionally use new language features of J2SE 5.0 as well. This means that classes must be compiled with a -source argument: piccolo% java -source 1.5 javathreads/examples/ch02/example1/*.java While the -source argument is not needed for a great many of our examples, we always use it for consistency. Running the examples requires using the entire package name for the main class: piccolo% java javathreads.examples.ch02.example1.SwingTypeTester It is always possible to run each example in this fashion: first compile all the files in the example directory and then run the specific class. This can lead to a lot of typing. To make this easier, we’ve also supplied an Ant build file that can be used to compile and run all examples. ANT On its home page, http://ant.apache.org, the authors describe Ant as “a Java-based build tool. In theory, it is kind of like Make, but without Make’s wrinkles.” Because it’s written in Java, it is portable; its design makes it extensible as well. To use Ant, you must download it from http://ant.apache.org/. Unzip the downloaded archive, and add the ant binary directory to your path. You don’t need to know anything about how ant works in order to use it for our examples, but if you’re planning on doing serious Java development, learning about ant is well worth the (rather minimal) effort. The ant build file we supply has a target for each example that you can run; these targets are named by chapter and example number. For instance, to run the first example from Chapter 2, you can execute this command: piccolo% ant ch2-ex1 The ant target for each example is also listed at the end of each chapter. Some examples require a command- line argument. When using ant, these arguments have a default value (specified in the build.xml file) and can be overridden on the command line. For example, to specify the number of threads for a particular example in Chapter 5, you can run the example like this: piccolo% ant -DCalcThreadCount=5 ch5-ex4 The properties and their defaults are listed at the end of the chapter, like this: <property name="CalcThreadCount" value="10"/> Why Threads? The notion of threading is so ingrained in Java that it’s almost impossible to write even the simplest programs in Java without creating and using threads. And many of the classes in the Java API are already threaded, so often you are using multiple threads without realizing it. Historically, threading was first exploited to make certain programs easier to write: if a program can be split into separate tasks, it’s often easier to program the algorithm as separate tasks or threads. Programs that fall into this category are typically specialized and deal with multiple independent tasks. The relative rareness of these types of programs makes threading in this category a specialized skill. Often, these programs were written as separate processes using operating system-dependent communication tools such as signals and shared memory spaces to communicate between processes. This approach increased system complexity. The popularity of threading increased when graphical interfaces became the standard for desktop computers because the threading system allowed the user to perceive better program performance. The introduction of threads into these platforms didn’t make the programs any faster, but it created an illusion of faster performance for the user, who now had a dedicated thread to service input or display output. In the 1990s, threaded programs began to exploit the growing number of computers with multiple processors. 12
Programs that require a lot of CPU processing are natural candidates for this category since a calculation that requires one hour on a single-processor machine could (at least theoretically) run in half an hour on a two- processor machine or 15 minutes on a four-processor machine. All that is required is that the program be written to use multiple threads to perform the calculation. Although computers with multiple processors have been around for a long time, we’re now seeing these machines become cheap enough to be very widely available. The advent of less expensive machines with multiple processors, and of operating systems that provide programmers with thread libraries to exploit those processors, has made threaded programming a hot topic as developers move to extract every benefit from these machines. Until Java, much of the interest in threading centered on using threads to take advantage of multiple processors on a single machine. However, threading in Java often has nothing at all to do with multiprocessor machines and their capabilities; in fact, the first Java virtual machines were unable to take advantage of multiple processors on a machine. Modern Java virtual machines no longer suffer from this limitation, and a multithreaded Java program takes advantage of all the CPUs available on its host machine. However, even if your Java program is destined to be run on a machine with a single CPU, threading is still very important. One reason that threading is important in Java is that, until JDK 1.4, Java had no concept of asynchronous behavior for I/O. This meant that many of the programming techniques you’ve become accustomed to using in typical programs were not applicable in Java; instead, until recently, Java programmers had to use threading techniques to handle asynchronous behavior. Another reason is the graphical nature of Java; since the beginning, Java was intended to be used in browsers, and it is used widely in environments with graphical user interfaces. Programmers need to understand threads merely to be able to use the asynchronous nature of the GUI library. This is not to say there aren’t other times when threads are a handy programming technique in Java; certainly it’s easy to use Java for a program that implements an algorithm that naturally lends itself to threading. And many Java programs implement multiple independent behaviors. The next few sections cover some of the circumstances in which Java threads are a needed component of the program — either directly using threads or using Java libraries that make heavy use of threads. Many of these circumstances are due to the need for asynchronous behavior or the elegance that threading lends to the program. Nonblocking I/O In Java, as in most programming languages, when you try to get input from the user, you execute a read() method specifying the user’s terminal (System.in in Java). When the program executes the read() method, the program typically waits until the user types at least one character before it continues and executes the next statement. This type of I/O is called blocking I/O : the program blocks until some data is available to satisfy the read() method. This type of behavior is often undesirable. If you’re reading data from a network socket, that data is often not available when you want to read it: the data may have been delayed in transit over the network, or you may be reading from a network server that sends data only periodically. If the program blocks when it tries to read from the socket, it’s unable to do anything else until the data is actually available. If the program has a user interface that contains a button and the user presses the button while the program is executing the read() method, nothing happens: the program is unable to handle the mouse events and execute the event processing method associated with the button. This can be very frustrating for the user, who thinks the program has hung. Traditionally, three techniques are available to handle this situation: I/O Multiplexing Developers often take all input sources and use a system call like select() to notify them when data is available from a particular source. This allows input to be handled much like an event from the user (in fact, many graphical toolkits use this method transparently to the developer, who simply registers a callback function that is called whenever data is available from a particular source). Beginning with JDK 1.4, this feature is provided with the NIO library — a library that allows a programmer to deal with I/O in an asynchronous manner. Polling Polling allows a developer to test if data is available from a particular source. If data is available, the data can be read and processed: if it is not, the program can perform another task. Polling can be done either explicitly — with a system call like poll() — or, in some systems, by making the read( ) function return an indication that no data is immediately available. Polling is also supported by the NIO library of JDK 1.4. In the traditional I/O library, there is only limited support for polling via the available() method of the FilterInputStream class. Unfortunately, this method does not have the rich semantics that polling typically has in most operating systems and is not recommended 13
as a reliable technique to determine whether data is actually available. Signals A file descriptor representing the input source can often be set so that an asynchronous signal is delivered to the program when data is available on that input source. This signal interrupts the program, which processes the data and then returns to whatever task it had been doing. Java does not support this technique. While the issue of blocking I/O can conceivably occur with any data source, it occurs most frequently with network sockets. If you’re used to programming sockets, you’ve probably used one of these techniques to read from a socket, but perhaps not to write to one. Many developers, used to programming on a local area network (LAN), are vaguely aware that writing to a socket may also block, but it’s a possibility that many of them ignore because it happens only under certain circumstances, such as a backlog in getting data onto the network. This backlog rarely happens on a fast LAN, but if you’re using Java to program sockets over the Internet, the chances of this backlog happening are greatly increased, thus increasing the chance of blocking while attempting to write data onto the network. In Java, you may need two threads to handle the socket: one to read from the socket and one to write to it. As a result, writing a program that uses I/O means either using multiple threads to handle traditional (blocking) I/O or using the NIO library (or both). The NIO library itself is very complex — much more complex than the thread library. Consequently, it is still often easier to set up a separate thread to read the data (using traditional I/O) from a blocking data source. This separate thread can block when data isn’t available, and the other thread(s) in the Java program can process events from the user or perform other tasks. On the other hand, there are many times when the added complexity of the NIO library is worthwhile and where the proliferation of threads required to process thousands of data sources would be untenable. But using the NIO library doesn’t remove all threading complexities; that library has its own thread-related issues. We examine the threading issues related to I/O in depth in Chapter 12. Alarms and Timers Traditional operating systems typically provide some sort of timer or alarm call: the program sets the timer and continues processing. When the timer expires, the program receives some sort of asynchronous signal that notifies the program of the timer’s expiration. In early versions of Java, the programmer had to set up a separate thread to simulate a timer. That thread slept for the duration of a specified time interval and then notified other threads when the timer expired. As Java matured, multiple new classes that provide this functionality were added. These new classes use the exact same technique to provide the functionality, but they hide (at least some of) the threading details from the developer. For complete details on these timers, see Chapter 11. Independent Tasks A Java program is often called on to perform independent tasks. In the simplest case, a single applet may perform two independent animations for a web page. A more complex program would be a calculation server that performs calculations on behalf of several clients simultaneously. In either case, while it is possible to write a single-threaded program to perform multiple tasks, it’s easier and more elegant to place each task in its own thread. The complete answer to the question “Why threads?” really lies in this category. As programmers, we’re trained to think linearly and often fail to see simultaneous paths that our program might take. But there’s no reason why processes that we’ve conventionally thought of in a single-threaded fashion need necessarily remain so: when the Save button in a word processor is pressed, we typically have to wait a few seconds until we can continue. Worse yet, the word processor may periodically perform an autosave, which invariably interrupts the flow of typing and disrupts the thought process. In a threaded word processor, the save operation would be in a separate thread so that it didn’t interfere with the work flow. As you become accustomed to writing programs with multiple threads, you’ll discover many circumstances in which adding a separate thread makes your algorithms more elegant and your programs more responsive. Parallelizable Algorithms With the advent of virtual machines that can use multiple CPUs simultaneously, Java has become a useful platform for developing programs that use algorithms that can be parallelized; that is, running one iteration of the loop on one CPU while another iteration of the loop is simultaneously running on another CPU. Dependencies between the data that each iteration of the loop needs may prohibit a particular loop from being parallelized, and there may be other reasons why a loop should not be parallelized. But for many programs with CPU-intensive loops, parallelizing the loop greatly speeds up the execution of the program when it is run on a machine with multiple processors. 14
Many languages have compilers that support automatic parallelization of loops, but as yet, Java does not. However, as we’ll see in Chapter 15, parallelizing a loop by hand is often not a difficult task. Summary In this chapter, we’ve provided a basic overview of where we’re going in our exploration of threaded programs. Threading is a basic feature of Java, and we’ve seen some of the reasons why it’s more important to Java than to other programming platforms. In the next few chapters, we look into the basics of thread programming. We start by looking at how threads are created and used in an application. [1] Note the version number change or perhaps we should say leap. The predecessor to J2SE 5.0 was J2SE 1.4. In beta, J2SE 5.0 was also known as J2SE 1.5. In this book, we refer to earlier versions using the more commonly used phrase JDK 1.x rather than J2SE 1.x. 15
Chapter 2. Thread Creation and Management In this chapter, we cover all the basics about threads: what a thread is, how threads are created, and some details about the lifecycle of a thread. If you’re new to threading, this chapter gives you all the information you need to create some basic threads. Be aware, however, that we take some shortcuts with our examples in this chapter: it’s impossible to write a good threaded program without taking into account the data synchronization issues that we discuss in Chapter 3. This chapter gets you started on understanding how threads work; coupled with the next chapter, you’ll have the ability to start using threads in your own Java applications. What Is a Thread? Let’s start by discussing what a thread actually is. A thread is an application task that is executed by a host computer. The notion of a task should be familiar to you even if the terminology is not. Suppose you have a Java program to compute the factorial of a given number: package javathreads.examples.ch02.example1; public class Factorial { public static void main(String[] args) { int n = Integer.parseInt(args[0]); System.out.print(n + "! is "); int fact = 1; while (n > 1) fact *= n--; System.out.println(fact); } } When your computer runs this application, it executes a sequence of commands. At an abstract level, that list of commands looks like this: Convert args[0] to an integer. Store that integer in a location called n. Print some text. Store 1 in a location called fact. Test if n is greater than 1. If it is, multiply the value stored in fact by the value stored in n and decrement n by 1. If it isn’t, print out the value stored in fact. Behind the scenes, what happens is somewhat more complicated since the instructions that are executed are actually machine-level assembly instructions; each of our logical steps requires many machine instructions to execute. But the principle is the same: an application is executed as a series of instructions. The execution path of these instructions is a thread.[1] Consequently, every computer program has at least one thread: the thread that executes the body of the application. In a Java application, that thread is called the main thread, and it begins executing statements with the first statement of the main() method of your class. In other programming languages, the starting point may be different, and the terminology may be different, but the basic idea is the same. STARTING A PROGRAM For Java applications, execution begins with the main() method of the class being run. What about other Java programs? In applets, servlets, and other J2EE programs, execution still begins with the main() method of the program, but in this case, the main() method belongs to the Java plug-in or J2EE container. Those containers then call your code through predetermined, well-known locations. An applet is called via its init() and start() methods; a servlet is called through its doGet() and doPost() methods, and so on. In any case, the procedure is the same: execution of your code begins with the first statements and proceeds by a single thread sequentially. In a Java program, it turns out that every program has more than one thread. Many of these are threads that developers are unaware of, such as threads that perform garbage collection and compile Java bytecodes into machine-level instructions. In a graphical application, other threads handle input from the mouse and keyboard and play audio. Your Java application is highly threaded, whether you program additional threads into it or not. Returning to our example, let’s suppose that we wrote a program that performed two tasks: one calculated the factorial of a number and one calculated the square root of that number. These are two separate tasks, and so you could choose to write them as two separate threads. Now how would your application run? 16
The answer to that depends on the conditions under which the application is run. The Java virtual machine now has two distinct lists of instructions to execute. One list calculates the factorial of a number (as we outlined earlier), and the other list calculates the square root of the number. The Java virtual machine executes both of these lists almost simultaneously. Although you may not have thought about it in these terms, this situation should also be familiar to you from the computer on which you normally do your work. The program you use to read your email is a list of instructions that the computer executes. So too is the program that you use to listen to music. You’re able to read email and listen to music at the same time because the computer executes both lists of instructions at about the same time. In fact, what happens is that the computer executes a handful of instructions from the email application and then executes a handful of instructions from the music program. It continues this procedure, switching back and forth between lists of instructions, and it does that quickly enough so that both programs appear to be executing at the same time. Quickly enough, in fact, that there are no gaps in the music. If you happen to have more than one CPU on your computer, the lists of instructions can execute at exactly the same time: one list can execute on each CPU. But multiple CPUs aren’t necessary to give the appearance of simultaneous execution or to exploit the power of threading. A single CPU can appear to execute both lists of instructions in parallel, letting you read your email and listen to music simultaneously. Threads behave exactly the same way. In our case, the Java virtual machine executes a handful of the instructions to calculate the factorial and then executes a handful of instructions to calculate the square root, and so on. So threads are simply tasks that you want to execute at roughly the same time. Why, then, write an application with multiple threads? Why not just write multiple applications? The answer lies in the fact that because threads are running in the same application, they share the same memory space in the computer. This allows them to share information seamlessly. Your email program and your music application don’t communicate very well. At best, you can copy and paste some data (like the name of a file) between the two. That allows you to double- click on an MP3 attachment in your email and play it in your music application, but the only information that is shared between the two is the name of the MP3 file. This type of cooperation is shown in Figure 2-1. Figure 2-1. Processes in a multitasking environment In a multitasking environment, data in the programs is separated by default: each has its own stack for local variables, and each has its own area for objects and other data. All the programs can access various types of shared memory (including the name of the MP3 file that you clicked on in your email program). The shared memory is restricted to information put there by other programs, and the APIs to access it are usually quite different than the APIs used to access other data in the program. This type of data sharing is fine for dissimilar programs, but it is inadequate for other programs. Consider a network server that sends stock quotes to multiple clients. Sending a quote to a client is a discrete task and may be done in a separate thread. In fact, if the client must acknowledge the quote, then sending the data in separate threads is highly recommended: you don’t want all clients to wait for a particularly slow client to respond. Here the data to be sent to the clients is the same; you don’t want each client to require a separate server process which must then replicate all the data held by every other server process. Instead, you want multiple threads in one program so that they may share data and each perform discrete tasks on that data. That type of sharing is shown in Figure 2-2. 17
Figure 2-2. Threads in a multithreaded environment Conceptually, the threads seem to be the same as programs. The key difference here is that the global memory is the entire Java heap: threads can transparently share access between any object in the heap. Each thread still has its own space for local variables (variables specific to the method the thread is executing). But objects are shared automatically and transparently. A thread, then, is a discrete task that operates on data shared with other threads. Creating a Thread Threads can be created in two ways: using the Thread class and using the Runnable interface. The Runnable interface (generally) requires an instance of a thread, so we begin with the Thread class. In this section, we start developing a typing game. The idea of this game is that characters are displayed and the user must type the key corresponding to the character. Through the next few chapters, we add enough logic to score the user’s accuracy and timing and provide enough feedback so that the user can improve her typing skills. For now, we are content to display a random character and display the character the user types in response. This application has two tasks: one task must continually display a random character and then pause for some random period of time. The second task must display characters typed on the keyboard. The Example Architecture Before we delve into the threading aspects of our code, let’s look at a few utility classes used in this and subsequent examples. The typing game has two sources for characters: characters that the user types at the keyboard and characters that are randomly generated. Both sources of characters are represented by this interface: package javathreads.examples.ch02; public interface CharacterSource { public void addCharacterListener(CharacterListener cl); public void removeCharacterListener(CharacterListener cl); public void nextCharacter( ); } We want to use the standard Java pattern of event listeners to handle these characters: a listener can register with a particular source and be notified when a new character is available. That requires the typical set of Java classes for a listener pattern, starting with the listener interface: package javathreads.examples.ch02; public interface CharacterListener { public void newCharacter(CharacterEvent ce); } The events themselves are objects of this class: package javathreads.examples.ch02; public class CharacterEvent { public CharacterSource source; public int character; public CharacterEvent(CharacterSource cs, int c) { source = cs; character = c; } } And finally, we need a helper class that fires the events when appropriate: package javathreads.examples.ch02; 18
import java.util.*; public class CharacterEventHandler { private Vector listeners = new Vector( ); public void addCharacterListener(CharacterListener cl) { listeners.add(cl); } public void removeCharacterListener(CharacterListener cl) { listeners.remove(cl); } public void fireNewCharacter(CharacterSource source, int c) { CharacterEvent ce = new CharacterEvent(source, c); CharacterListener[] cl = (CharacterListener[] ) listeners.toArray(new CharacterListener[0]); for (int i = 0; i < cl.length; i++) cl[i].newCharacter(ce); } } In our graphical display, one canvas registers to be notified when the user types a character; that canvas displays the character. A second canvas registers to be notified when a random character is generated; it displays the new characters as they are generated. We’ve chosen this design pattern since, in later examples, multiple objects will be interested in knowing when new characters are generated. Here’s a utility class that can display a given character: package javathreads.examples.ch02; import java.awt.*; import javax.swing.*; public class CharacterDisplayCanvas extends JComponent implements CharacterListener { protected FontMetrics fm; protected char[] tmpChar = new char[1]; protected int fontHeight; public CharacterDisplayCanvas( ) { setFont(new Font("Monospaced", Font.BOLD, 18)); fm = Toolkit.getDefaultToolkit( ).getFontMetrics(getFont( )); fontHeight = fm.getHeight( ); } public CharacterDisplayCanvas(CharacterSource cs) { this( ); setCharacterSource(cs); } public void setCharacterSource(CharacterSource cs) { cs.addCharacterListener(this); } public Dimension preferredSize( ) { return new Dimension(fm.getMaxAscent( ) + 10, fm.getMaxAdvance( ) + 10); } public synchronized void newCharacter(CharacterEvent ce) { tmpChar[0] = (char) ce.character; repaint( ); } protected synchronized void paintComponent(Graphics gc) { Dimension d = getSize( ); gc.clearRect(0, 0, d.width, d.height); if (tmpChar[0] == 0) return; int charWidth = fm.charWidth((int) tmpChar[0]); gc.drawChars(tmpChar, 0, 1, (d.width - charWidth) / 2, fontHeight); } } Although this class has no references to threads, it still has thread-related issues: namely, we had to use the synchronized keyword for some of the methods. This is because of something known as a race condition (see Chapter 3). REAL-LIFE RACE CONDITIONS In order to understand threaded programming fully, you must understand how threads run and are created (the topic of this chapter) as well as how they interact with data (the topic of the next chapter). Any interesting threaded program uses both 19
features. This means that a forward reference to some details (like the synchronized keyword) is unavoidable. This is the essence of a race condition: two things need to complete sequentially in order to end up in a coherent state. This race condition also applies to Swing programming. We use Swing components in our examples because they make the applications more relevant and interesting. Swing components have some special thread programming considerations, as we’ll see over the next few chapters, but we won’t be able to explain them fully until we understand more about how multiple threads work. The Thread Class Now we can program our first task (and our first thread): a thread that periodically generates a random character. In Java, threads are represented by instances of the java.lang.Thread class. They are created just like any other Java object, but they contain a special method that tells the virtual machine to begin executing the code of the thread as a separate “list.” Here’s a partial API of the Thread class, showing its constructors and its execution-related methods: package java.lang; public class Thread implements Runnable { public Thread( ); public Thread(Runnable target); public Thread(ThreadGroup group, Runnable target); public Thread(String name); public Thread(ThreadGroup group, String name); public Thread(Runnable target, String name); public Thread(ThreadGroup group, Runnable target, String name); public Thread(ThreadGroup group, Runnable target, String name, long stackSize); public void start( ); public void run( ); } As you see, threads are created with four pieces of information: Thread name The name of a thread is part of the information shown when a thread object is printed. Otherwise, it has no significance, so give your threads names that make sense to you when you see them printed. The default name for a thread is Thread-N, where N is a unique number. Runnable target We discuss runnables in depth later in this chapter. A runnable object is the list of instructions that the thread executes. By default, this is the information in the run() method of the thread itself. Note that the Thread class itself implements the Runnable interface. Thread group Thread groups are an advanced topic (see Chapter 13). For the vast majority of applications, thread groups are unimportant. By default, a thread is assigned to the same thread group as the thread that calls the constructor. Stack size Every thread has a stack where it stores temporary variables as it executes methods. Everything related to the stack size of a thread is platform-dependent: its default stack size, the range of legal values for the stack size, the optimal value for the stack size, and so on. Use of the stack size in portable programs is highly discouraged. For more information, see Chapter 13. We can use these methods of the Thread class to create our first thread: package javathreads.examples.ch02.example2; import java.util.*; import javathreads.examples.ch02.*; public class RandomCharacterGenerator extends Thread implements CharacterSource { static char[] chars; static String charArray = "abcdefghijklmnopqrstuvwxyz0123456789"; static { chars = charArray.toCharArray( ); } Random random; CharacterEventHandler handler; public RandomCharacterGenerator( ) { random = new Random( ); handler = new CharacterEventHandler( ); } 20
The above is a preview of the first 20 pages. Register to read the complete e-book.