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

886

Multithreading

Chapter 15

15.13 (Optional) Discovering Design Patterns: Concurrent

Design Patterns

Many additional design patterns have been created since the publication of the gang-of-four book, which introduced patterns involving object-oriented systems. Some of these new patterns involve specific object-oriented systems, such as concurrent, distributed or parallel systems. In this section, we discuss concurrency patterns to conclude our discussion of multithreaded programming.

Concurrency Design Patterns

Multithreaded programming languages such as Java allow designers to specify concurrent activities—that is, those that operate in parallel with one another. Designing concurrent systems improperly can introduce concurrency problems. For example, two objects attempting to alter shared data at the same time could corrupt that data. In addition, if two objects wait for one another to finish tasks, and if neither can complete their task, these objects could potentially wait forever—a situation called deadlock. Using Java, Doug Lea1 and Mark Grand2 created concurrency patterns for multithreaded design architectures to prevent various problems associated with multithreading. We provide a partial list of these design patterns:

The Single-Threaded Execution design pattern (Grand, 98) prevents several threads from invoking the same method of another object concurrently. In Java, the synchronized keyword (discussed in Chapter 15) can be used to apply this pattern.

The Guarded Suspension design pattern (Lea, 97) suspends a thread’s activity and resumes that thread’s activity when some condition is satisfied. Lines 137–147 and lines 95–109 of class RandomCharacters (Fig. 15.17) use this design pat- tern—methods wait and notify suspend and resume, respectively, the program threads, and line 98 toggles the variable that the condition evaluates.

The Balking design pattern (Lea, 97) ensures that a method will balk—that is, return without performing any actions—if an object occupies a state that cannot execute that method. A variation of this pattern is that the method throws an exception describing why that method is unable to execute—for example, a method throwing an exception when accessing a data structure that does not exist.

The Read/Write Lock design pattern (Lea, 97) allows multiple threads to obtain concurrent read access on an object but prevents multiple threads from obtaining concurrent write access on that object. Only one thread at a time may obtain write access on an object—when that thread obtains write access, the object is locked to all other threads.

The Two-Phase Termination design pattern (Grand, 98) uses a two-phase termination process for a thread to ensure that a thread frees resources—such as other spawned threads—in memory (phase one) before termination (phase two). In

1.D. Lea, Concurrent Programing in Java, Second Edition: Design Principles and Patterns. Massachusetts: Addison-Wesley. November 1999.

2.M. Grand, Patterns in Java; A Catalog of Reusable Design Patterns Illustrated with UML. New York: John Wiley and Sons, 1998.

Chapter 15

Multithreading

887

Java, a Thread object can use this pattern in method run. For instance, method run can contain an infinite loop that is terminated by some state change—upon termination, method run can invoke a private method responsible for stopping any other spawned threads (phase one). The thread then terminates after method run terminates (phase two).

In “Discovering Design Patterns” Section 17.10, we return to the gang-of-four design patterns. Using the material introduced in Chapters 16 and 17, we identify those classes in package java.io and java.net that use design patterns.

SUMMARY

Computers perform operations concurrently, such as compiling a program, printing a file and receiving e-mail messages over a network.

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 concurrency that computers perform today is normally implemented as operating system “primitives” available only to highly experienced “systems programmers.”

Java makes concurrency primitives available to the programmer.

Applications contain threads of execution, each thread designating a portion of a program that may execute concurrently with other threads. This capability is called multithreading.

Java provides a low-priority garbage collector thread that reclaims dynamically allocated memory that is no longer needed. The garbage collector runs when processor time is available and there are no higher priority runnable threads. The garbage collector runs immediately when the system is out of memory to try to reclaim memory.

Method run contains the code that controls a thread’s execution.

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

Method interrupt is called to interrupt a thread.

Method isAlive returns true if start has been called for a given thread and the thread is not dead (i.e., the run method has not completed execution).

Method setName sets the name of the Thread. Method getName returns the name of the Thread. Method toString returns a String consisting of the name of the thread, the priority of the thread and the thread’s group.

Thread static method currentThread returns a reference to the executing Thread.

Method join waits for the Thread on which join is called to die before the current Thread can proceed.

Waiting can be dangerous; it can lead to two serious problems called deadlock and indefinite postponement. Indefinite postponement is also called starvation.

A thread that was just created is in the born state. The thread remains in this state until the thread’s start method is called; this causes the thread to enter the ready state.

A highest priority-ready thread enters the running state when the system assigns a processor to the thread.

A thread enters the dead state when its run method completes or terminates for any reason. The system eventually will dispose of a dead thread.

888

Multithreading

Chapter 15

A running thread enters the blocked state when the thread issues an input/output request. A blocked thread becomes ready when the I/O it is waiting for completes. A blocked thread cannot use a processor even if one is available.

When a running method calls wait, the thread enters a waiting state for the particular object in which the thread was running. A thread in the waiting state for a particular object becomes ready on a call to notify issued by another thread associated with that object.

Every thread in the waiting state for a given object becomes ready on a call to notifyAll by another thread associated with that object.

Every Java thread has a priority in the range Thread.MIN_PRIORITY (a constant of 1) and Thread.MAX_PRIORITY (a constant of 10). By default, each thread is given priority

Thread.NORM_PRIORITY (a constant of 5).

Some Java platforms support a concept called timeslicing and some do not. Without timeslicing, threads of equal priority run to completion before their peers get a chance to execute. With timeslicing, each thread receives a brief burst of processor time called a quantum during which that thread can execute. At the completion of the quantum, even if that thread has not finished executing, the processor is taken away from that thread and given to the next thread of equal priority, if one is available.

The job of the Java scheduler is to keep a highest priority thread running at all times and, if timeslicing is available, to ensure that several equally high-priority threads each execute for a quantum in round-robin fashion.

A thread’s priority can be adjusted with the setPriority method. Method getPriority returns the thread’s priority.

A thread can call the yield method to give other threads a chance to execute.

Every object that has synchronized methods has a monitor. The monitor lets only one thread at a time execute a synchronized method on the object.

A thread executing in a synchronized method may determine that it cannot proceed, so the thread voluntarily calls wait. This removes the thread from contention for the processor and from contention for the object.

A thread that has called wait is awakened by a thread that calls notify. The notify acts as a signal to the waiting thread that the condition the waiting thread has been waiting for is now (or could be) satisfied, so it is acceptable for that thread to reenter the monitor.

A daemon thread serves other threads. When only daemon threads remain in a program, Java will exit. If a thread is to be a daemon, it must be set as such before its start method is called.

To support multithreading in a class derived from some class other than Thread, implement the Runnable interface in that class.

Implementing the Runnable interface gives us the ability to treat the new class as a Runnable object (just like inheriting from a class allows us to treat our subclass as an object of its superclass). As with deriving from the Thread class, the code that controls the thread is placed in the run method.

A thread with a Runnable class is created by passing to the Thread class constructor a reference to an object of the class that implements the Runnable interface. The Thread constructor registers the run method of the Runnable object as the method to be invoked when the thread begins execution.

Class ThreadGroup contains the methods for creating and manipulating groups of related threads in a program.

Chapter 15

Multithreading

889

TERMINOLOGY

asynchronous threads blocked (state of a thread) blocked on I/O

busy wait

child thread group circular buffer

concurrent execution of threads condition variable

consumer consumer thread context

currentThread method daemon thread

dead (state of a thread) deadlock

destroy method dumpStack method Error class ThreadDeath execution context fixed-priority scheduling

garbage collection by a low-priority thread getName method

getParent method of ThreadGroup class highest priority runnable thread

I/O completion

IllegalArgumentException IllegalMonitorStateException IllegalThreadStateException indefinite postponement

inherit thread priority interrupt method interrupted method

InterruptedException InterruptedException class interthread communication isAlive method

isDaemon method isInterrupted method join method

kill a thread

MAX_PRIORITY(10) memory leak

MIN_PRIORITY(1) monitor

multiple inheritance multiprocessing multithreaded program multithreaded server multithreading

new (state of a thread) nonpreemptive scheduling

NORM_PRIORITY(5) notify method notifyAll method parallelism

parent thread parent thread group

preemptive scheduling priority of a thread producer thread

producer/consumer relationship programmer-defined thread quantum

round-robin scheduling run method

Runnable interface (in java.lang package) runnable state (of a thread)

running (thread state) scheduler

scheduling a thread setDaemon method setName method setPriority method shared objects single-threaded language single-threaded program

sleep method of Thread class sleeping state (of a thread) start method

starvation synchronization synchronized method thread

Thread class (in java.lang package) thread group

thread priority thread safe thread states

thread synchronization

Thread.MAX_PRIORITY

Thread.MIN_PRIORITY

Thread.NORM_PRIORITY Thread.sleep() ThreadDeath exception ThreadGroup class timeslicing

wait method yield method

890

Multithreading

Chapter 15

SELF-REVIEW EXERCISES

15.1Fill in the blanks in each of the following statements:

a)

C and C++ are

 

-threaded languages whereas Java is a

 

-threaded

 

language.

 

 

 

 

 

 

 

 

 

 

 

 

 

b)

Java provides a

 

 

thread that reclaims dynamically allocated memory.

c)

Java eliminates most

 

 

 

 

errors that occur commonly in languages like C and

 

C++ when dynamically allocated memory is not explicitly reclaimed by the program.

d)

Three reasons a thread that is alive could be not runnable (i.e., blocked) are

,

 

 

and

 

.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

e)

A thread enters the dead state when

 

.

 

 

 

 

 

f)

A thread’s priority can be changed with the

 

 

method.

 

 

 

g)A thread may give up the processor to a thread of the same priority by calling the method.

h)To wait for a designated number of milliseconds and resume execution, a thread should

call the

 

 

method.

i) The

 

 

method moves a thread in the object’s waiting state to the ready state.

15.2State whether each of the following is true or false. If false, explain why.

a)A thread is not runnable if it is dead.

b)In Java, a higher priority runnable thread will preempt threads of lower priority.

c)The Windows and Windows NT Java systems use timeslicing. Therefore, they can enable threads to preempt threads of the same priority.

d)Threads may yield to threads of lower priority.

ANSWERS TO SELF-REVIEW EXERCISES

15.1a) single, multi. b) garbage collector. c) memory leak. d) waiting, sleeping, blocked for input/output. e) its run method terminates. f) setPriority. g) yield. h) sleep. i) notify.

15.2a) True. b) True. c) False. Timeslicing allows a thread to execute until its timeslice (or quantum) expires. Then other threads of equal priority can execute. d) False. Threads can only yield to threads of equal priority.

EXERCISES

15.3State whether each of the following is true or false. If false, explain why.

a)The sleep method does not consume processor time while a thread sleeps.

b)Declaring a method synchronized guarantees that deadlock cannot occur.

c)Java provides a powerful capability called multiple inheritance.

d)Thread methods suspend and resume are deprecated.

15.4Define each of the following terms.

a)thread

b)multithreading

c)ready state

d)blocked state

e)preemptive scheduling

f)Runnable interface

g)monitor

h)notify method

i)producer/consumer relationship

15.5a) List each of the reasons stated in this chapter for using multithreading.

b)List additional reasons for using multithreading.

Chapter 15

Multithreading

891

15.6List each of the three reasons given in the text for entering the blocked state. For each of these, describe how the program will normally leave the blocked state and enter the runnable state.

15.7Distinguish between preemptive scheduling and nonpreemptive scheduling. Which does Java use?

15.8What is timeslicing? Give a fundamental difference in how scheduling is performed on Java systems that support timeslicing vs. scheduling on Java systems that do not support timeslicing.

15.9Why would a thread ever want to call yield?

15.10What aspects of developing Java applets for the World Wide Web encourage applet designers to use yield and sleep abundantly?

15.11If you choose to write your own start method, what must you be sure to do to ensure that your threads start up properly?

15.12Distinguish among each of the following means of pausing threads:

a)busy wait

b)sleep

c)blocking I/O

15.13Write a Java statement that tests if a thread is alive.

15.14a) What is multiple inheritance?

b)Explain why Java does not offer multiple inheritance.

c)What feature does Java offer instead of multiple inheritance?

d)Explain the typical use of this feature.

e)How does this feature differ from abstract classes?

15.15Distinguish between the notions of extends and implements.

15.16Discuss each of the following terms in the context of monitors:

a)monitor

b)producer

c)consumer

d)wait

e)notify

f)InterruptedException

g)synchronized

15.17(Tortoise and the Hare) In the Chapter 7 exercises, you were asked to simulate the legendary race of the tortoise and the hare. Implement a new version of that simulation, this time placing each of the animals in a separate thread. At the start of the race call the start methods for each of the threads. Use wait, notify and notifyAll to synchronize the animals’ activities.

15.18(Multithreaded, Networked, Collaborative Applications) In Chapter 17, we cover networking in Java. A multithreaded Java application can communicate concurrently with several host computers. This creates the possibility of being able to build some interesting kinds of collaborative applications. In anticipation of studying networking in Chapter 17, develop proposals for several possible multithreaded networked applications. After studying Chapter 17, implement some of those applications.

15.19Write a Java program to demonstrate that as a high-priority thread executes, it will delay the execution of all lower priority threads.

15.20If your system supports timeslicing, write a Java program that demonstrates timeslicing among several equal-priority threads. Show that a lower priority thread’s execution is deferred by the timeslicing of the higher-priority threads.

15.21Write a Java program that demonstrates a high priority thread using sleep to give lower priority threads a chance to run.

892

Multithreading

Chapter 15

15.22If your system does not support timeslicing, write a Java program that demonstrates two threads using yield to enable one another to execute.

15.23Two problems that can occur in systems like Java, that allow threads to wait, are deadlock, in which one or more threads will wait forever for an event that cannot occur, and indefinite postponement, in which one or more threads will be delayed for some unpredictably long time. Give an example of how each of these problems can occur in a multithreaded Java program.

15.24(Readers and Writers) This exercise asks you to develop a Java monitor to solve a famous problem in concurrency control. This problem was first discussed and solved by P. J. Courtois, F. Heymans and D. L. Parnas in their research paper, “Concurrent Control with Readers and Writers,” Communications of the ACM, Vol. 14, No. 10, October 1971, pp. 667–668. The interested student might also want to read C. A. R. Hoare’s seminal research paper on monitors, “Monitors: An Operating System Structuring Concept,” Communications of the ACM, Vol. 17, No. 10, October 1974, pp. 549–557. Corrigendum, Communications of the ACM, Vol. 18, No. 2, February 1975, p. 95. [The readers and writers problem is discussed at length in Chapter 5 of the author’s book: Deitel, H. M., Operating Systems, Reading, MA: Addison-Wesley, 1990.]

a)With multithreading, many threads can access shared data; as we have seen, access to shared data needs to be synchronized carefully to avoid corrupting the data.

b)Consider an airline-reservation system in which many clients are attempting to book seats on particular flights between particular cities. All of the information about flights and seats is stored in a common database in memory. The database consists of many entries, each representing a seat on a particular flight for a particular day between particular cities. In a typical airline-reservation scenario, the client will probe around in the database looking for the “optimal” flight to meet that client’s needs. So a client may probe the database many times before deciding to book a particular flight. A seat that was available during this probing phase could easily be booked by someone else before the client has a chance to book it. In that case, when the client attempts to make the reservation, the client will discover that the data has changed and the flight is no longer available.

c)The client probing around the database is called a reader. The client attempting to book the flight is called a writer. Clearly, any number of readers can be probing shared data at once, but each writer needs exclusive access to the shared data to prevent the data from being corrupted.

d)Write a multithreaded Java program that launches multiple reader threads and multiple writer threads, each attempting to access a single reservation record. A writer thread has two possible transactions, makeReservation and cancelReservation. A reader has one possible transaction, queryReservation.

e)First implement a version of your program that allows unsynchronized access to the reservation record. Show how the integrity of the database can be corrupted. Next implement a version of your program that uses Java monitor synchronization with wait and notify to enforce a disciplined protocol for readers and writers accessing the shared reservation data. In particular, your program should allow multiple readers to access the shared data simultaneously when no writer is active. But if a writer is active, then no readers should be allowed to access the shared data.

f)Be careful. This problem has many subtleties. For example, what happens when there are several active readers and a writer wants to write? If we allow a steady stream of readers to arrive and share the data, they could indefinitely postpone the writer (who may become tired of waiting and take his or her business elsewhere). To solve this problem, you might decide to favor writers over readers. But here, too, there is a trap, because a steady stream of writers could then indefinitely postpone the waiting readers, and they, too, might choose to take their business elsewhere! Implement your monitor with the following methods: startReading, which is called by any reader who wants to begin accessing

Chapter 15

Multithreading

893

a reservation; stopReading to be called by any reader who has finished reading a reservation; startWriting to be called by any writer who wants to make a reservation and stopWriting to be called by any writer who has finished making a reservation.

15.25Write a program that bounces a blue ball inside an applet. The ball should be initiated with a mousePressed event. When the ball hits the edge of the applet, the ball should bounce off the edge and continue in the opposite direction.

15.26Modify the program of Exercise 15.25 to add a new ball each time the user clicks the mouse. Provide for a minimum of 20 balls. Randomly choose the color for each new ball.

15.27Modify the program of Exercise 15.26 to add shadows. As a ball moves, draw a solid black oval at the bottom of the applet. You may consider adding a 3-D effect by increasing or decreasing the size of each ball when a ball hits the edge of the applet.

15.28Modify the program of Exercise 15.25 or 15.26 to bounce the balls off each other when they

collide.