System, but they may not be reproduced for publication



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

 Chapter 10 Self Test
1.
Why does Java define both byte and character streams?
2.
Even though console input and output is text­based, why does Java still use byte
streams for this purpose?
3.
Show how to open a file for reading bytes.
4.
Show how to open a file for reading characters.
5.
Show how to open a file for random­access I/O.
6.
How can you convert a numeric string such as "123.23" into its binary equivalent?
7.
Write a program that copies a text file. In the process, have it convert all spaces into


hyphens. Use the byte stream file classes. Use the traditional approach to closing a file
by explicitly calling close( ).
8.
Rewrite the program described in question 7 so that it uses the character stream
classes. This time, use the try­with­resources statement to automatically close the file.
9.
What type of stream is System.in?
10.
What does the read( ) method of InputStream return when an attempt is made
to read at the end of the stream?
11.
What type of stream is used to read binary data?
12.
Reader and Writer are at the top of the ____________ class hierarchies.
13.
The try­with­resources statement is used for ___________ ____________
____________.
14.
If you are using the traditional method of closing a file, then closing a file within a
finally block is generally a good approach. True or False?
15.
Can local variable type inference be used when declaring the resource in a try­with­
resources statement?


Chapter 11
Multithreaded Programming
Key Skills & Concepts

Understand multithreading fundamentals

Know the Thread class and the Runnable interface

Create a thread

Create multiple threads

Determine when a thread ends
History
Topics
Tutorials
Offers & Deals
Highlights
Settings
Support
Sign Out


A

Use thread priorities

Understand thread synchronization

Use synchronized methods

Use synchronized blocks

Communicate between threads

Suspend, resume, and stop threads
lthough Java contains many innovative features, one of its most exciting is its built­in
support for multithreaded programming. A multithreaded program contains two or
more parts that can run concurrently. Each part of such a program is called a thread,
and each thread defines a separate path of execution. Thus, multithreading is a
specialized form of multitasking.
MULTITHREADING FUNDAMENTALS
There are two distinct types of multitasking: process­based and thread­based. It is
important to understand the difference between the two. A process is, in essence, a
program that is executing. Thus, process­based multitasking is the feature that allows
your computer to run two or more programs concurrently. For example, it is process­
based multitasking that allows you to run the Java compiler at the same time you are
using a text editor or browsing the Internet. In process­based multitasking, a program
is the smallest unit of code that can be dispatched by the scheduler.
In a thread­based multitasking environment, the thread is the smallest unit of
dispatchable code. This means that a single program can perform two or more tasks at
once. For instance, a text editor can be formatting text at the same time that it is
printing, as long as these two actions are being performed by two separate threads.
Although Java programs make use of process­based multitasking environments,
process­based multitasking is not under the control of Java. Multithreaded
multitasking is.
A principal advantage of multithreading is that it enables you to write very efficient
programs because it lets you utilize the idle time that is present in most programs. As
you probably know, most I/O devices, whether they be network ports, disk drives, or
the keyboard, are much slower than the CPU. Thus, a program will often spend a


majority of its execution time waiting to send or receive information to or from a
device. By using multithreading, your program can execute another task during this idle
time. For example, while one part of your program is sending a file over the Internet,
another part can be reading keyboard input, and still another can be buffering the next
block of data to send.
As you probably know, over the past few years, multiprocessor and multicore systems
have become commonplace. Of course, single­processor systems are still in widespread
use. It is important to understand that Java’s multithreading features work in both
types of systems. In a single­core system, concurrently executing threads share the
CPU, with each thread receiving a slice of CPU time. Therefore, in a single­core system,
two or more threads do not actually run at the same time, but idle CPU time is utilized.
However, in multiprocessor/multicore systems, it is possible for two or more threads to
actually execute simultaneously. In many cases, this can further improve program
efficiency and increase the speed of certain operations.
A thread can be in one of several states. It can be running. It can be ready to run as
soon as it gets CPU time. A running thread can be suspended, which is a temporary halt
to its execution. It can later be resumed. A thread can be blocked when waiting for a
resource. A thread can be terminated, in which case its execution ends and cannot be
resumed.
Along with thread­based multitasking comes the need for a special type of feature
called synchronization, which allows the execution of threads to be coordinated in
certain well­defined ways. Java has a complete subsystem devoted to synchronization,
and its key features are also described here.
If you have programmed for operating systems such as Windows, then you are already
familiar with multithreaded programming. However, the fact that Java manages
threads through language elements makes multithreading especially convenient. Many
of the details are handled for you.
THE THREAD CLASS AND RUNNABLE INTERFACE
Java’s multithreading system is built upon the Thread class and its companion
interface, Runnable. Both are packaged in java.lang. Thread encapsulates a thread
of execution. To create a new thread, your program will either extend Thread or
implement the Runnable interface.
The Thread class defines several methods that help manage threads. Here are some of


the more commonly used ones (we will be looking at these more closely as they are
used):
All processes have at least one thread of execution, which is usually called the main
thread, because it is the one that is executed when your program begins. Thus, the
main thread is the thread that all of the preceding example programs in the book have
been using. From the main thread, you can create other threads.
CREATING A THREAD
You create a thread by instantiating an object of type Thread. The Thread class
encapsulates an object that is runnable. As mentioned, Java defines two ways in which
you can create a runnable object:

You can implement the Runnable interface.

You can extend the Thread class.
Most of the examples in this chapter will use the approach that implements Runnable.
However, 
Try This 11­1
shows how to implement a thread by extending Thread.
Remember: Both approaches still use the Thread class to instantiate, access, and
control the thread. The only difference is how a thread­enabled class is created.
The Runnable interface abstracts a unit of executable code. You can construct a
thread on any object that implements the Runnable interface. Runnable defines only
one method called run( ), which is declared like this:
Inside run( ), you will define the code that constitutes the new thread. It is important
to understand that run( ) can call other methods, use other classes, and declare


variables just like the main thread. The only difference is that run( ) establishes the
entry point for another, concurrent thread of execution within your program. This
thread will end when run( ) returns.
After you have created a class that implements Runnable, you will instantiate an
object of type Thread on an object of that class. Thread defines several constructors.
The one that we will use first is shown here:
Thread(Runnable threadOb)
In this constructor, threadOb is an instance of a class that implements the Runnable
interface. This defines where execution of the thread will begin.
Once created, the new thread will not start running until you call its start( ) method,
which is declared within Thread. In essence, start( ) executes a call to run( ). The
start( ) method is shown here:
void start( )
Here is an example that creates a new thread and starts it running:


Let’s look closely at this program. First, MyThread implements Runnable. This
means that an object of type MyThread is suitable for use as a thread and can be
passed to the Thread constructor.


Inside run( ), a loop is established that counts from 0 to 9. Notice the call to sleep( ).
The sleep( ) method causes the thread from which it is called to suspend execution for
the specified period of milliseconds. Its general form is shown here:
static void sleep(long milliseconds) throws InterruptedException
The number of milliseconds to suspend is specified in milliseconds. This method can
throw an InterruptedException. Thus, calls to it must be wrapped in a try block.
The sleep( ) method also has a second form, which allows you to specify the period in
terms of milliseconds and nanoseconds if you need that level of precision. In run( ),
sleep( ) pauses the thread for 400 milliseconds each time through the loop. This lets
the thread run slow enough for you to watch it execute.
Inside main( ), a new Thread object is created by the following sequence of
statements:
As the comments suggest, first an object of MyThread is created. This object is then
used to construct a Thread object. This is possible because MyThread implements
Runnable. Finally, execution of the new thread is started by calling start( ). This
causes the child thread’s run( ) method to begin. After calling start( ), execution
returns to main( ), and it enters main( )’s for loop. Notice that this loop iterates 50
times, pausing 100 milliseconds each time through the loop. Both threads continue
running, sharing the CPU in single­CPU systems, until their loops finish. The output
produced by this program is as follows. Because of differences between computing
environments, the precise output that you see may differ slightly from that shown here:


There is another point of interest to notice in this first threading example. To illustrate
the fact that the main thread and mt execute concurrently, it is necessary to keep
main( ) from terminating until mt is finished. Here, this is done through the timing
differences between the two threads. Because the calls to sleep( ) inside main( )’s for
loop cause a total delay of 5 seconds (50 iterations times 100 milliseconds), but the
total delay within run( )’s loop is only 4 seconds (10 iterations times 400
milliseconds), run( ) will finish approximately 1 second before main( ). As a result,
both the main thread and mt will execute concurrently until mt ends. Then, about 1
second later main( ) ends.
Although this use of timing differences to ensure that main( ) finishes last is sufficient
for this simple example, it is not something that you would normally use in practice.
Java provides much better ways of waiting for a thread to end. It is, however, sufficient
for the next few programs. Later in this chapter, you will see a better way for one thread
to wait until another completes.
One other point: In a multithreaded program, you often will want the main thread to be
the last thread to finish running. As a general rule, a program continues to run until all
of its threads have ended. Thus, having the main thread finish last is not a requirement.
It is, however, often a good practice to follow—especially when you are first learning
about threads.
One Improvement and Two Simple Variations
The preceding program demonstrates the fundamentals of creating a Thread based on
a Runnable and then starting the thread. The approach shown in that program is
perfectly valid and is often exactly what you will want. However, two simple variations


can make MyThread more flexible and easier to use in some cases. Furthermore, you
may find that these variations are helpful when you create your own Runnable classes.
It is also possible to make one significant improvement to MyThread that takes
advantage of another feature of the Thread class. Let’s begin with the improvement.
In the preceding program, notice that an instance variable called thrdName is defined
by MyThread and is used to hold the name of the thread. However, there is no need
for MyThread to store the name of the thread since it is possible to give a name to a
thread when it is created. To do so, use this version of Thread’s constructor:
Thread(Runnable threadOb, String name)
Ask the Expert
Q
: You state that in a multithreaded program, one will often want the
main thread to finish last. Can you explain?
A
: The main thread is a convenient place to perform the orderly shutdown of your
program, such as the closing of files. It also provides a well­defined exit point for
your program. Therefore, it often makes sense for it to finish last. Fortunately, as
you will soon see, it is trivially easy for the main thread to wait until the child
threads have completed.
Here, name becomes the name of the thread. You can obtain the name of the thread by
calling getName( ) defined by Thread. Its general form is shown here:
final String getName( )
Giving a thread a name when it is created provides two advantages. First, there is no
need for you to use a separate variable to hold the name because Thread already
provides this capability. Second, the name of the thread will be available to any code
that holds a reference to the thread. One other point: although not needed by this
example, you can set the name of a thread after it is created by using setName( ),
which is shown here:
final void setName(String threadName)
Here, threadName specifies the new name of the thread.


As mentioned, there are two variations that can, depending on the situation, make
MyThread more convenient to use. First, it is possible for the MyThread constructor
to create a Thread object for the thread, storing a reference to that thread in an
instance variable. With this approach, the thread is ready to start as soon as the
MyThread constructor returns. You simply call start( ) on the Thread instance
encapsulated by MyThread.
The second variation offers a way to have a thread begin execution as soon as it is
created. This approach is useful in cases in which there is no need to separate thread
creation from thread execution. One way to accomplish this for MyThread is to
provide a static factory method that:
1. creates a new MyThread instance,
2. calls start( ) on the thread associated with that instance,
3. and then returns a reference to the newly created MyThread object.
With this approach, it becomes possible to create and start a thread through a single
method call. This can streamline the use of MyThread, especially in cases in which
several threads must be created and started.
The following version of the preceding program incorporates the changes just
described:


This version produces the same output as before. However, notice that now MyThread
no longer contains the name of the thread. Instead, it provides an instance variable
called thrd that holds a reference to the Thread object created by MyThread’s
constructor, shown here:
Thus, after MyThread’s constructor executes, thrd will contain a reference to the
newly created thread. To start the thread, you will simply call start( ) on thrd.
Next, pay special attention to the createAndStart( ) factory method, shown here:


When this method is called, it creates a new instance of MyThread called myThrd. It
then calls start( ) on myThrd’s copy of thrd. Finally, it returns a reference to the
newly created MyThread instance. Thus, once the call to createAndStart( ) returns,
the thread will already have been started. Therefore, in main( ), this line creates and
begins the execution of a thread in a single call:
Because of the convenience that createAndStart( ) offers, it will be used by several of
the examples in this chapter. Furthermore, you may find it helpful to adapt such a
method for use in thread­based applications of your own. Of course, in cases in which
you want a thread’s execution to be separate from its creation, you can simply create a
MyThread object and then call start( ) later.
Ask the Expert
Q
: Earlier, you used the term factory method and showed one example
in the method called createAndStart( ). Can you give me a more general
definition?
A
: Yes. In general, a factory method is a method that returns an object of a class.
Typically, factory methods are static methods of a class. Factory methods are
useful in a variety of situations. Here are some examples. As you just saw in the
case of createAndStart( ), a factory method enables an object to be constructed
and then set to some specified state prior to being returned to the caller. Another
type of factory method is used to provide an easy­to­remember name that indicates
the variety of object that is being constructed. For example, assuming a class called
Line, you might have factory methods that create lines of specific colors, such as
createRedLine( ) or createBlueLine( ). Instead of having to remember a
potentially complex call to a constructor, you can simply use the factory method
whose name indicates the type of line you want. In some cases it is also possible for


a factory method to reuse an object, rather than constructing a new one. As you
will see as you advance in your study of Java, factory methods are common in the
Java API library.
Try This 11­1
Extending Thread
Implementing Runnable is one way to create a class that can instantiate thread
objects. Extending Thread is the other. In this project, you will see how to extend
Thread by creating a program functionally similar to the UseThreads program
shown at the start of this chapter.
When a class extends Thread, it must override the run( ) method, which is the entry
point for the new thread. It must also call start( ) to begin execution of the new
thread. It is possible to override other Thread methods, but doing so is not required.
1. Create a file called ExtendThread.java. Begin this file with the following lines:
Notice that MyThread now extends Thread instead of implementing Runnable.
2. Add the following MyThread constructor:
Here, super is used to call this version of Thread’s constructor:
Thread(String threadName)
Here, threadName specifies the name of the thread. As explained previously, Thread
provides the ability to hold a thread’s name. Thus, no instance variable is required by


MyThread to store the name.
3. Conclude MyThread by adding the following run( ) method:
Notice the calls to getName( ). Because ExtendThread extends Thread, it can
directly call all of Thread’s methods, including the getName( ) method.
4. Next, add the ExtendThread class shown here:


In main( ), notice how an instance of MyThread is created and then started with
these two lines:
Because MyThread now implements Thread, start( ) is called directly on the
MyThread instance, mt.
5. Here is the complete program. Its output is the same as the UseThreads example,
but in this case, Thread is extended rather than Runnable being implemented.


6. When extending Thread, it is also possible to include the ability to create and start
a thread in one step by using a static factory method, similar to that used by the


ThreadVariations program shown earlier. To try this, add the following method to
MyThread:
As you can see, this method creates a new MyThread instance with the specified
name, calls start( ) on that thread, and returns a reference to the thread. To try
createAndStart( ), replace these two lines in main( ):
with this line:
After making these changes, the program will run the same as before, but you will be
creating and starting the thread using a single method call.
CREATING MULTIPLE THREADS
The preceding examples have created only one child thread. However, your program
can spawn as many threads as it needs. For example, the following program creates
three child threads:


Ask the Expert


Q
: Why does Java have two ways to create child threads (by extending
Thread or implementing Runnable) and which approach is better?
A
: The Thread class defines several methods that can be overridden by a derived
class. Of these methods, the only one that must be overridden is run( ). This is, of
course, the same method required when you implement Runnable. Some Java
programmers feel that classes should be extended only when they are being
expanded or customized in some way. So, if you will not be overriding any of
Thread’s other methods, it is probably best to simply implement Runnable.
Also, by implementing Runnable, you enable your thread to inherit a class other
than Thread.
Sample output from this program follows:


As you can see, once started, all three child threads share the CPU. Notice that in this
run the threads are started in the order in which they are created. However, this may
not always be the case. Java is free to schedule the execution of threads in its own way.
Of course, because of differences in timing or environment, the precise output from the
program may differ, so don’t be surprised if you see slightly different results when you


try the program.
DETERMINING WHEN A THREAD ENDS
It is often useful to know when a thread has ended. For example, in the preceding
examples, for the sake of illustration it was helpful to keep the main thread alive until
the other threads ended. In those examples, this was accomplished by having the main
thread sleep longer than the child threads that it spawned. This is, of course, hardly a
satisfactory or generalizable solution!
Fortunately, Thread provides two means by which you can determine if a thread has
ended. First, you can call isAlive( ) on the thread. Its general form is shown here:
final boolean isAlive( )
The isAlive( ) method returns true if the thread upon which it is called is still
running. It returns false otherwise. To try isAlive( ), substitute this version of
MoreThreads for the one shown in the preceding program:
This version produces output that is similar to the previous version, except that main(


) ends as soon as the other threads finish. The difference is that it uses isAlive( ) to
wait for the child threads to terminate. Another way to wait for a thread to finish is to
call join( ), shown here:
final void join( ) throws InterruptedException
This method waits until the thread on which it is called terminates. Its name comes
from the concept of the calling thread waiting until the specified thread joins it.
Additional forms of join( ) allow you to specify a maximum amount of time that you
want to wait for the specified thread to terminate.
Here is a program that uses join( ) to ensure that the main thread is the last to stop:


Sample output from this program is shown here. Remember that when you try the
program, your precise output may vary slightly.


As you can see, after the calls to join( ) return, the threads have stopped executing.
THREAD PRIORITIES


THREAD PRIORITIES
Each thread has associated with it a priority setting. A thread’s priority determines, in
Yüklə 83 Mb.

Dostları ilə paylaş:
1   ...   45   46   47   48   49   50   51   52   ...   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ə