Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Java How to Program, Fourth Edition - Deitel H., Deitel P.pdf
Скачиваний:
58
Добавлен:
24.05.2014
Размер:
14.17 Mб
Скачать

15

Multithreading

Objectives

To understand the notion of multithreading.

To appreciate how multithreading can improve performance.

To understand how to create, manage and destroy threads.

To understand the life cycle of a thread.

To study several examples of thread synchronization.

To understand thread priorities and scheduling.

To understand daemon threads and thread groups.

The spider’s touch, how exquisitely fine!

Feels at each thread, and lives along the line.

Alexander Pope

A person with one watch knows what time it is; a person with two watches is never sure.

Proverb

Conversation is but carving!

Give no more to every guest,

Than he’s able to digest.

Jonathan Swift

Learn to labor and to wait.

Henry Wadsworth Longfellow

The most general definition of beauty…Multeity in Unity.

Samuel Taylor Coleridge

838

Multithreading

Chapter 15

Outline

15.1Introduction

15.2Class Thread: An Overview of the Thread Methods

15.3Thread States: Life Cycle of a Thread

15.4Thread Priorities and Thread Scheduling

15.5Thread Synchronization

15.6Producer/Consumer Relationship without Thread Synchronization

15.7Producer/Consumer Relationship with Thread Synchronization

15.8Producer/Consumer Relationship: The Circular Buffer

15.9Daemon Threads

15.10Runnable Interface

15.11Thread Groups

15.12(Optional Case Study) Thinking About Objects: Multithreading

15.13(Optional) Discovering Design Patterns: Concurrent Design Patterns

Summary • Terminology • Self-Review Exercises • Answers to Self-Review Exercises • Exercises

15.1 Introduction

It would be nice if we could “do one thing at a time” and “do it well,” but that is simply not how the world works. The human body performs a great variety of operations in parallel, or as we will say throughout this chapter, concurrently. Respiration, blood circulation and digestion, for example, can occur concurrently. All of the senses—seeing, touching, smelling, tasting and hearing—can all occur concurrently. An automobile can be accelerating, turning, air conditioning and playing music concurrently. Computers, too, perform operations concurrently. It is common today for desktop personal computers to be compiling a program, printing a file and receiving e-mail messages over a network concurrently.

Concurrency is important in our lives. Ironically, though, most programming languages do not enable programmers to specify concurrent activities. Rather, programming languages generally provide only a simple set of control structures that enable programmers to perform one action at a time then proceed to the next action after the previous one is finished. The kind of concurrency that computers perform today normally is implemented as operating systems “primitives” available only to highly experienced “systems programmers.”

The Ada programming language developed by the United States Department of Defense made concurrency primitives widely available to defense contractors building command and control systems. But Ada has not been widely used in universities and commercial industry.

Java is unique among popular general-purpose programming languages in that it makes concurrency primitives available to the applications programmer. The programmer specifies that applications contain threads of execution, each thread designating a portion of a program that may execute concurrently with other threads. This capability, called multithreading, gives the Java programmer powerful capabilities not available in C and C++, the languages on which Java is based. C and C++ are called single-threaded languages.

Chapter 15 Multithreading 839

[Note: On many computer platforms, C and C++ programs can perform multithreading by using system specific code libraries.]

Software Engineering Observation 15.1

Unlike many languages that do not have built-in multithreading (such as C and C++) and must therefore make calls to operating system multithreading primitives, Java includes multithreading primitives as part of the language itself (actually in classes Thread, ThreadGroup, ThreadLocal and ThreadDeath of the java.lang package). This encourages the use of multithreading among a larger part of the applications-programming community.

We will discuss many applications of concurrent programming. When programs download large files such as audio clips or video clips from the World Wide Web, we do not want to wait until an entire clip is downloaded before starting the playback. So we can put multiple threads to work: One that downloads a clip and another that plays the clip so that these activities, or tasks, may proceed concurrently. To avoid choppy playback, we will coordinate the threads so that the player thread does not begin until there is a sufficient amount of the clip in memory to keep the player thread busy.

Another example of multithreading is Java’s automatic garbage collection. In C and C++, the programmer is responsible for reclaiming dynamically allocated memory. Java provides a garbage collector thread that reclaims dynamically allocated memory that the program no longer needs.

Testing and Debugging Tip 15.1

In C and C++, programmers must provide explicit statements that reclaim dynamically allocated memory. When memory is not reclaimed (because a programmer forgets to do so, because of a logic error or because an exception diverts program control), this results in an all-too-common error called a memory leak that can eventually exhaust the supply of free memory and may cause premature program termination. Java’s automatic garbage collection eliminates the vast majority of memory leaks, that is, those that are due to orphaned (unreferenced) objects.

Java’s garbage collector runs as a low-priority thread. When Java determines that there are no longer any references to an object, it marks the object for eventual garbage collection. The garbage-collector thread runs when processor time is available and when there are no higher priority runnable threads. However, the garbage collector will run immediately when the system is out of memory.

Performance Tip 15.1

Setting an object reference to null marks that object for eventual garbage collection (if there are no other references to the object). This can help conserve memory in a system in which a local variable that refers to an object does not go out of scope because the method in which it appears executes for a lengthy period.

Writing multithreaded programs can be tricky. Although the human mind can perform many functions concurrently, humans find it difficult to jump between parallel “trains of thought.” To see why multithreading can be difficult to program and understand, try the following experiment: open three books to page 1. Now try reading the books concurrently. Read a few words from the first book, then read a few words from the second book, then read a few words from the third book, then loop back and read the next few words from the first book, and so on. After a brief time, you will appreciate the challenges of multithreading: switching between books, reading briefly, remembering your place in each

840

Multithreading

Chapter 15

book, moving the book you are reading closer so you can see it, pushing books you are not reading aside, and amidst all this chaos, trying to comprehend the content of the books!

Performance Tip 15.2

A problem with single-threaded applications is that lengthy activities must complete before other activities can begin. In a multithreaded application, threads can share a processor (or set of processors), so that multiple tasks are performed in parallel.

Although Java is perhaps the world’s most portable programming language, certain portions of the language are nevertheless platform dependent. In particular, there are differences among the first three Java platforms implemented, namely the Solaris implementation and the Win32 implementations (i.e., Windows-based implementations for Windows 95 and Windows NT).

The Solaris Java platform runs a thread of a given priority to completion or until a higher priority thread becomes ready. At that point preemption occurs (i.e., the processor is given to the higher priority thread while the previously running thread must wait).

In the 32-bit Java implementations for Windows 95 and Windows NT, threads are timesliced. This means that each thread is given a limited amount of time (called a time quantum) to execute on a processor, and when that time expires the thread is made to wait while all other threads of equal priority get their chances to use their quantum in roundrobin fashion. Then the original thread resumes execution. Thus, on Windows 95 and Windows NT, a running thread can be preempted by a thread of equal priority; whereas, on the Solaris implementation, a running Java thread can only be preempted by a higher priority thread. Future Solaris Java systems are expected to perform timeslicing as well.

Portability Tip 15.1

Java multithreading is platform dependent. Thus, a multithreaded application could behave differently on different Java implementations.

15.2 Class Thread: An Overview of the Thread Methods

In this section, we overview the various thread-related methods in the Java API. We use many of these methods in live-code examples throughout the chapter. The reader should refer to the Java API directly for more details on using each method, especially the exceptions thrown by each method.

Class Thread (package java.lang) has several constructors. The constructor

public Thread( String threadName )

constructs a Thread object whose name is threadName. The constructor

public Thread()

constructs a Thread whose name is "Thread-" concatenated with a number, like

Thread-1, Thread-2, and so on.

The code that “does the real work” of a thread is placed in its run method. The run method can be overridden in a subclass of Thread or it may be implemented in a Runnable object; Runnable is an important Java interface that we study in Section 15.10.

A program launches a thread’s execution by calling the thread’s start method, which, in turn, calls method run. After start launches the thread, start returns to its