System, but they may not be reproduced for publication


partially defeats the advantages of programming for a multithreaded environment. A



Yüklə 83 Mb.
Pdf görüntüsü
səhifə51/82
tarix19.04.2023
ölçüsü83 Mb.
#106251
1   ...   47   48   49   50   51   52   53   54   ...   82
Java A Beginner’s Guide, Eighth Edition ( PDFDrive )


partially defeats the advantages of programming for a multithreaded environment. A
better solution is to have T temporarily relinquish control of the object, allowing
another thread to run. When R becomes available, T can be notified and resume
execution. Such an approach relies upon some form of interthread communication in
which one thread can notify another that it is blocked and be notified that it can resume
execution. Java supports interthread communication with the wait( ), notify( ), and
notifyAll( ) methods.
The wait( ), notify( ), and notifyAll( ) methods are part of all objects because they
are implemented by the Object class. These methods should be called only from within
a synchronized context. Here is how they are used. When a thread is temporarily


blocked from running, it calls wait( ). This causes the thread to go to sleep and the
monitor for that object to be released, allowing another thread to use the object. At a
later point, the sleeping thread is awakened when some other thread enters the same
monitor and calls notify( ), or notifyAll( ).
Following are the various forms of wait( ) defined by Object:
final void wait( ) throws InterruptedException
final void wait(long millis) throws InterruptedException
final void wait(long millis, int nanos) throws InterruptedException
The first form waits until notified. The second form waits until notified or until the
specified period of milliseconds has expired. The third form allows you to specify the
wait period in terms of nanoseconds.
Here are the general forms for notify( ) and notifyAll( ):
final void notify( )
final void notifyAll( )
A call to notify( ) resumes one waiting thread. A call to notifyAll( ) notifies all
threads, with the scheduler determining which thread gains access to the object.
Before looking at an example that uses wait( ), an important point needs to be made.
Although wait( ) normally waits until notify( ) or notifyAll( ) is called, there is a
possibility that in very rare cases the waiting thread could be awakened due to a
spurious wakeup. The conditions that lead to a spurious wakeup are complex and
beyond the scope of this book. However, the Java API documentation recommends that
because of the remote possibility of a spurious wakeup, calls to wait( ) should take
place within a loop that checks the condition on which the thread is waiting. The
following example shows this technique.
An Example That Uses wait( ) and notify( )
To understand the need for and the application of wait( ) and notify( ), we will create
a program that simulates the ticking of a clock by displaying the words Tick and Tock
on the screen. To accomplish this, we will create a class called TickTock that contains
two methods: tick( ) and tock( ). The tick( ) method displays the word "Tick", and
tock( ) displays "Tock". To run the clock, two threads are created, one that calls tick( )


and one that calls tock( ). The goal is to make the two threads execute in a way that the
output from the program displays a consistent "Tick Tock"—that is, a repeated pattern
of one tick followed by one tock.


Here is the output produced by the program:


Let’s take a close look at this program. The heart of the clock is the TickTock class. It
contains two methods, tick( ) and tock( ), which communicate with each other to
ensure that a Tick is always followed by a Tock, which is always followed by a Tick, and
so on. Notice the state field. When the clock is running, state will hold either the
string "ticked" or "tocked", which indicates the current state of the clock. In main( ), a
TickTock object called tt is created, and this object is used to start two threads of
execution.
The threads are based on objects of type MyThread. Both the MyThread constructor
and the createAndStart( ) method are passed two arguments. The first becomes the
name of the thread. This will be either "Tick" or "Tock". The second is a reference to the
TickTock object, which is tt in this case. Inside the run( ) method of MyThread, if
the name of the thread is "Tick", then calls to tick( ) are made. If the name of the
thread is "Tock", then the tock( ) method is called. Five calls that pass true as an
argument are made to each method. The clock runs as long as true is passed. A final
call that passes false to each method stops the clock.
The most important part of the program is found in the tick( ) and tock( ) methods of
TickTock. We will begin with the tick( ) method, which, for convenience, is shown
here:


First, notice that tick( ) is modified by synchronized. Remember, wait( ) and
notify( ) apply only to synchronized methods. The method begins by checking the
value of the running parameter. This parameter is used to provide a clean shutdown of
the clock. If it is false, then the clock has been stopped. If this is the case, state is set to
"ticked" and a call to notify( ) is made to enable any waiting thread to run. We will
return to this point in a moment.
Assuming that the clock is running when tick( ) executes, the word "Tick" is displayed,
state is set to "ticked", and then a call to notify( ) takes place. The call to notify( )
allows a thread waiting on the same object to run. Next, wait( ) is called within a
while loop. The call to wait( ) causes tick( ) to suspend until another thread calls
notify( ). Therefore, the loop will not iterate until another thread calls notify( ) on the
same object. As a result, when tick( ) is called, it displays one "Tick", lets another
thread run, and then suspends.
The while loop that calls wait( ) checks the value of state, waiting for it to equal
"tocked", which will be the case only after the tock( ) method executes. As explained,
using a while loop to check this condition prevents a spurious wakeup from incorrectly
restarting the thread. If state does not equal "tocked" when wait( ) returns, it means
that a spurious wakeup occurred, and wait( ) is simply called again.
The tock( ) method is an exact copy of tick( ) except that it displays "Tock" and sets
state to "tocked". Thus, when entered, it displays "Tock", calls notify( ), and then
waits. When viewed as a pair, a call to tick( ) can only be followed by a call to tock( ),
which can only be followed by a call to tick( ), and so on. Therefore, the two methods
are mutually synchronized.
The reason for the call to notify( ) when the clock is stopped is to allow a final call to
wait( ) to succeed. Remember, both tick( ) and tock( ) execute a call to wait( ) after
displaying their message. The problem is that when the clock is stopped, one of the
methods will still be waiting. Thus, a final call to notify( ) is required in order for the
waiting method to run. As an experiment, try removing this call to notify( ) and watch
what happens. As you will see, the program will “hang,” and you will need to press
CTRL­C
to exit. The reason for this is that when the final call to tock( ) calls wait( ),
there is no corresponding call to notify( ) that lets tock( ) conclude. Thus, tock( ) just
sits there, waiting forever.
Before moving on, if you have any doubt that the calls to wait( ) and notify( ) are
actually needed to make the “clock” run right, substitute this version of TickTock into
the preceding program. It has all calls to wait( ) and notify( ) removed.


After the substitution, the output produced by the program will look like this:
Clearly, the tick( ) and tock( ) methods are no longer working together!
Ask the Expert
Q
: I have heard the term deadlock applied to misbehaving
multithreaded programs. What is it, and how can I avoid it? Also, what
is a race condition, and how can I avoid that, too?


A
: Deadlock is, as the name implies, a situation in which one thread is waiting for
another thread to do something, but that other thread is waiting on the first. Thus,
both threads are suspended, waiting on each other, and neither executes. This
situation is analogous to two overly polite people, both insisting that the other step
through a door first!
Avoiding deadlock seems easy, but it’s not. For example, deadlock can occur in
roundabout ways. The cause of the deadlock often is not readily understood just by
looking at the source code to the program because concurrently executing threads
can interact in complex ways at run time. To avoid deadlock, careful programming
and thorough testing is required. Remember, if a multithreaded program
occasionally “hangs,” deadlock is the likely cause.
A race condition occurs when two (or more) threads attempt to access a shared
resource at the same time, without proper synchronization. For example, one
thread may be writing a new value to a variable while another thread is
incrementing the variable’s current value. Without synchronization, the new value
of the variable will depend upon the order in which the threads execute. (Does the
second thread increment the original value or the new value written by the first
thread?) In situations like this, the two threads are said to be “racing each other,”
with the final outcome determined by which thread finishes first. Like deadlock, a
race condition can occur in difficult­to­discover ways. The solution is prevention:
careful programming that properly synchronizes access to shared resources.
SUSPENDING, RESUMING, AND STOPPING
THREADS
It is sometimes useful to suspend execution of a thread. For example, a separate thread
can be used to display the time of day. If the user does not desire a clock, then its thread
can be suspended. Whatever the case, it is a simple matter to suspend a thread. Once
suspended, it is also a simple matter to restart the thread.
The mechanisms to suspend, stop, and resume threads differ between early versions of
Java and more modern versions, beginning with Java 2. Prior to Java 2, a program used
suspend( ), resume( ), and stop( ), which are methods defined by Thread, to
pause, restart, and stop the execution of a thread. They have the following forms:
final void resume( )


final void suspend( )
final void stop( )
While these methods seem to be a perfectly reasonable and convenient approach to
managing the execution of threads, they must no longer be used. Here’s why. The
suspend( ) method of the Thread class was deprecated by Java 2. This was done
because suspend( ) can sometimes cause serious problems that involve deadlock. The
resume( ) method is also deprecated. It does not cause problems but cannot be used
without the suspend( ) method as its counterpart. The stop( ) method of the Thread
class was also deprecated by Java 2. This was done because this method too can
sometimes cause serious problems.
Since you cannot now use the suspend( ), resume( ), or stop( ) methods to control a
thread, you might at first be thinking that there is no way to pause, restart, or terminate
a thread. But, fortunately, this is not true. Instead, a thread must be designed so that
the run( ) method periodically checks to determine if that thread should suspend,
resume, or stop its own execution. Typically, this is accomplished by establishing two
flag variables: one for suspend and resume, and one for stop. For suspend and resume,
as long as the flag is set to “running,” the run( ) method must continue to let the
thread execute. If this variable is set to “suspend,” the thread must pause. For the stop
flag, if it is set to “stop,” the thread must terminate.
The following example shows one way to implement your own versions of suspend( ),
resume( ), and stop( ):




Sample output from this program is shown here. (Your output may differ slightly.)
Ask the Expert
Q
: Multithreading seems like a great way to improve the efficiency of
my programs. Can you give me any tips on effectively using it?
A
: The key to effectively utilizing multithreading is to think concurrently rather
than serially. For example, when you have two subsystems within a program that
are fully independent of each other, consider making them into individual threads.
A word of caution is in order, however. If you create too many threads, you can
actually degrade the performance of your program rather than enhance it.
Remember, overhead is associated with context switching. If you create too many
threads, more CPU time will be spent changing contexts than in executing your
program!
Here is how the program works. The thread class MyThread defines two Boolean
variables, suspended and stopped, which govern the suspension and termination of
a thread. Both are initialized to false by the constructor. The run( ) method contains a


synchronized statement block that checks suspended. If that variable is true, the
wait( ) method is invoked to suspend the execution of the thread. To suspend
execution of the thread, call mysuspend( ), which sets suspended to true. To
resume execution, call myresume( ), which sets suspended to false and invokes
notify( ) to restart the thread.
To stop the thread, call mystop( ), which sets stopped to true. In addition, mystop(
) sets suspended to false and then calls notify( ). These steps are necessary to stop a
suspended thread.
Try This 11­2
Using the Main Thread
All Java programs have at least one thread of execution, called the main thread, which
is given to the program automatically when it begins running. So far, we have been
taking the main thread for granted. In this project, you will see that the main thread
can be handled just like all other threads.
1. Create a file called UseMain.java.
2. To access the main thread, you must obtain a Thread object that refers to it. You do
this by calling the currentThread( ) method, which is a static member of Thread.
Its general form is shown here:
static Thread currentThread( )
This method returns a reference to the thread in which it is called. Therefore, if you call
currentThread( ) while execution is inside the main thread, you will obtain a
reference to the main thread. Once you have this reference, you can control the main
thread just like any other thread.
3. Enter the following program into the file. It obtains a reference to the main thread,
and then gets and sets the main thread’s name and priority.


4. The output from the program is shown here:


5. You need to be careful about what operations you perform on the main thread. For
example, if you add the following code to the end of main( ), the program will never
terminate because it will be waiting for the main thread to end!

Yüklə 83 Mb.

Dostları ilə paylaş:
1   ...   47   48   49   50   51   52   53   54   ...   82




Verilənlər bazası müəlliflik hüququ ilə müdafiə olunur ©genderi.org 2024
rəhbərliyinə müraciət

    Ana səhifə