|
Threads
Some Important Concepts
Why multi-threading:
Multi-threading makes a
program more efficient by minimizing the idle CPU
time. It is especially relevant in an interactive
program, or in any program that has to wait for some
event to occur or for some resource, to proceed
further. While one thread of the program waits,
another thread can continue executing code that is not
dependent on that event to occur.
Thread priorities:
Thread priorities are
integers that specify the relative priority of one
thread to another. These are used by the thread
scheduler to decide when each thread should be allowed
to run. (Thread scheduler is a piece of system code .
The scheduler might be part of JVM or host operating
system. It determines which thread is actually running
on each available CPU at a given time.) A thread’s
priority is used to decide when to switch from one
running thread to the next. This is called a
context-switch. The rules that determine when a
context switch takes place are:
-
A thread can voluntarily relinquish
control. This is done by explicitly yielding,
sleeping or blocking on pending I/O. In this
scenario, all other threads are examined, and the highest
priority thread that is ready to run is given the
CPU.
-
A thread can be preempted by a higher
priority thread. In this case, a lower priority
thread that does not yield the processor is simply
preempted – no matter what it is doing – by a higher
priority thread. This is called preemptive
multithreading.
In cases where two
threads with the same priority are competing for CPU
cycles, the outcome is system dependent. In operating
systems like windows, equal priority threads are
time-sliced automatically in round robin fashion. If
there is more than one waiting thread, scheduler
chooses one of them. There is no guarantee that the
longest waiting thread will be chosen. For other types
of operating systems, such as Solaris, threads of
equal priority must voluntarily yield control to their
peers. If they do not, other threads will not run.
The Java specification
states that threads must have priorities, but it does
not dictate precisely what the scheduler do about
priorities. This vagueness is a problem: algorithms
that rely on manipulating thread priorities might not
run consistently on all platforms. If you want smooth
multi-tasking, you should not rely on priorities of
threads. Also some types of tasks are CPU intensive.
Such threads dominate the CPU. For these types of
threads, it is better to yield control occasionally,
so that other threads can run too.
The value of thread
priorities must be within the range MIN_PRIORITY
(value=1) and MAX_PRIORITY (value=10). The default is
NORM_PRIORITY (value=5).
All newly created
threads have their priority set to that of the
creating thread.
Main thread:
When a Java program
starts up, one thread begins running immediately. This
thread is the one that is executed when your program
begins. The main thread is important for two reasons:
-
It is the thread from which other
child threads will be spawned.
-
It must be the last thread to finish
execution. When the main thread stops, your program
terminates.
The main thread is
created automatically when a program is started. It
can be controlled through a Thread object. To do so,
you must obtain a reference to it by calling the
method currentThread(), which is a public,
static member of Thread.
Thread t=Thread.currentThread();
When t is used as an
argument to println(), it displays the name of the
thread, its priority and the name of its group in that
order.
A thread group is a data
structure that controls the state of a collection of
threads as a whole. This process is managed by the
particular runtime environment.
Return of run ():
When the run() returns,
the thread has finished its task and is considered
dead. There is no way to restart it. If you want the
thread’s task to be performed again, you have to
construct and start a new thread instance. The dead
thread continues to exist; it is an object like any
other object, and you can still access its data and
call its methods. You just cannot make it run again.
Instead of using stop()
of thread class, if a thread might need to be killed
from another thread, then you should send it an
interrupt() from the killing method.
Thread states:
When you call start() on
a thread, the thread does not run immediately. It goes
into a ready-to-run state and stays there until the
scheduler moves it to the “running state”. Then the
run() is called. In the course of executing run(), the
thread may temporarily give up the CPU and enter some
other state for a while. The thread states are:
-
Running
-
Waiting
-
Sleeping
-
Suspended
-
Blocked
-
Dead
Controlling threads
Yielding
A call to the yield()
method causes the currently executing thread to move
to the ready state if the scheduler is willing to run
any other thread in its place. There are two possible
scenarios. If any other threads are in the ready
state, then the thread that just yielded might have to
wait a while before it gets to execute again. However,
if there are no other waiting threads, then the
yielding thread gets to continue executing
immediately. Most thread schedulers do not stop the
yielding thread from running in favor of a lower
priority thread. The yield() always causes the
currently executing thread to yield. Yielding allows a
time consuming thread to permit other threads to
execute.
Suspending
Suspending a thread is a
mechanism that allows any arbitrary thread to make
another thread un-runnable for an indefinite period of
time. The suspended thread becomes Runnable when some
other thread resumes it. It is very easy to cause
deadlock in a program using these methods, since a
thread does not have any control over when it is
suspended and it might be in a critical section,
holding an object lock at the time. The exact effect
of suspend() and resume() is much better implemented
using wait() and notify().
Sleeping
A sleeping thread passes
time without doing anything and without using the CPU.
A call to sleep() requests the currently executing
thread to cease executing for (approx) a specified
amount of time. The method sleep(), like yield(), is
static. Both operate on
currently executing thread.
When a thread has
finished sleeping, it does not continue execution. It
moves to a ready state and waits for the scheduler’s
permission.
The Thread class has a
method called interrupt(). A sleeping thread that
receives an interrupt() call moves immediately into
ready state; when it gets to run, it will execute its
InterruptedException handler.
Blocking
Many methods that
perform input/output have to wait for some occurrence
in the outside world before they can proceed; this
behavior is known as blocking. In general, if a method
needs to wait an indefinite time until some I/O takes
place, then a thread executing that method should
graciously step out of the Running state. All Java I/O
methods behave this way. A thread that has stepped out
in this fashion is said to be blocked.
Generally, if you see a
method with a name that suggests that it might do
nothing until something becomes ready, you should
expect that the caller thread might be blocked
becoming un-runnable and losing the CPU, when the
method is called.
A thread can also become
blocked if it fails to acquire the lock for a monitor
or if it issues a wait() call. Internally, most
blocking for I/O, is implemented using wait() and
notify().
Important
Exceptions:
IllegalThreadStateException: thrown to indicate that a
thread is not in an appropriate state for the
requested operation.
IllegalMonitorStateException: thrown to indicate that
a thread has attempted to wait on an object’s monitor
without owning the specified monitor.
Why stop (), suspend ()
and resume () are deprecated?
stop () is
inherently unsafe. Stopping a thread causes it to
unlock all the monitors that it has locked. (the
monitors are unlocked as the ThreadDeath exception
propagated up the stack.) If any of the objects
previously protected by these monitors were in an
inconsistent state, other threads may now view these
objects in an inconsistent state. Such objects are
said to be damaged. When threads operate on damaged
objects, arbitrary behavior can result. This behavior
may be subtle and difficult to detect, or it may be
pronounced. Unlike other unchecked exceptions,
ThreadDeath kills threads silently; thus, the user has
no warning that his program may be corrupted. The
corruption can manifest itself at any time after the
actual damage occurs, even after hours or days in the
future. Most uses of
stop ()
should be replaced by code that simply modifies some
variable to indicate that the target thread should
stop running. The target thread should check this
variable regularly, and return from its run method in
an orderly fashion if the variable indicates that it
is to stop running. To ensure prompt communication of
the stop () request, the variable must be
volatile (or access to
the variable must be synchronized.
suspend () is inherently
deadlock-prone. If the target thread holds a lock on
the monitor protecting a critical system resource when
it is suspended, no thread can access this resource
until the target thread is resumed. If the thread that
would resume the target thread attempts to lock this
monitor prior to calling resume (), deadlock results.
Such deadlocks typically manifest themselves as frozen
processes.
resume () exists solely
for suspend (). Therefore it has been deprecated too.
section7-1 | section7-2 | section7-3 | section7-4 | section7-5
Sections :
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11
If you wish to download the complete notes
(around 100 pages - pdf or doc file) for a small price, click here.
|