OOPJ MultiThreading NJB PDF
OOPJ MultiThreading NJB PDF
OOPJ MultiThreading NJB PDF
Multitasking
Multitasking is a process of executing multiple tasks simultaneously. We use
multitasking to utilize the CPU. Multitasking can be achieved in two ways:
1. Process-based Multitasking (Multiprocessing)
2. Thread-based Multitasking (Multithreading)
A thread can be in one of the 5 states. The life cycle of the thread is controlled by
JVM.
1. NEW:
The thread is in new state if you create an instance of Thread class but before the
invocation of start() method.
2. RUNNABLE(READY):
The thread is in runnable state after invocation of start() method, but the thread
scheduler has not selected it to be the running thread.
3. RUNNING:
The thread is in running state if the thread scheduler has selected it.
4. NON-RUNNABLE (BLOCKED, SLEEPING or WAITING):
This is the state when the thread is still alive, but is currently not eligible to run.
5. TERMINATED (DEAD):
A thread is in terminated or dead state when its run() method exits.
The Main Thread
When a Java program starts up, one thread begins running immediately.
This is usually called the main thread of your program, because it is the one that is
executed when your program begins.
The main thread is important for two reasons:
1. It is the thread from which other “child” threads will be spawned.
2. Often, it must be the last thread to finish execution because it performs various
shutdown actions.
• Although the main thread is created automatically when your 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.
• Its general form is shown here: static Thread currentThread( )
• This method returns a reference to the thread in which it is called.
• Once you have a reference to the main thread, you can control it just like any other
thread.
Creating a Thread
You create a thread by instantiating an object of type Thread.
Java defines two ways in which this can be accomplished:
1. You can implement the Runnable interface.
2. You can extend the Thread class, itself.
Extending Thread
The one way to create a thread is to create a new class that extends Thread, and
then to create an instance of that class.
The extending class 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.
• The easiest way to create a thread is to create a class that implements the Runnable
• interface.
• Runnable abstracts a unit of executable code.
• You can construct a thread on any
• object that implements Runnable.
• To implement Runnable, a class need only implement a single method called run(
), which is declared like this: public void run( )
• 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 can.
• 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 create a class that implements Runnable, you will instantiate an object of
type Thread from within that class. Thread defines several constructors. The one
that we will use is shown here: Thread(Runnable threadOb, String
threadName)
As you can see, once started, all three child threads share the CPU.
Notice the call to sleep(10000) in main( ).
This causes the main thread to sleep for ten seconds and ensures that it will finish
last.
Using isAlive( ) and join( )
For the main thread to finish last, calling sleep( ) within main( ), with a long
enough delay to ensure that all child threads terminate prior to the main thread, is
hardly a satisfactory solution, and it also raises a larger question:
How can one thread know when another thread has ended?
First, you can call isAlive( ) on the thread. This method is defined by Thread, and
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.
The method that you will more commonly use to wait for a thread to finish is
called 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. join(long milliseconds)
Thread priorities are used by the thread scheduler to decide when each thread
should be allowed to run.
In theory, over a given period of time, higher-priority threads get more CPU time
than lower-priority threads.
In practice, the amount of CPU time that a thread gets often depends on several
factors besides its priority.
In theory,
Threads of equal priority should get equal access to the CPU.
But, Java is designed to work in a wide range of environments some of which
implement multitasking fundamentally differently than others.
For safety, threads that share the same priority should yield control once in a
while.
This ensures that all threads have a chance to run under a non-preemptive
operating. system.
In practice,
Even in non-preemptive environments, most threads still get a chance to run,
because most threads inevitably encounter some blocking situation, such as
waiting for I/O.
When this happens, the blocked thread is suspended and other threads can run.
But, if you want smooth multithreaded execution, you are better off not relying on
this.
For the types of threads that dominate the CPU, you want to yield control
occasionally so that other threads can run.
To set a thread’s priority, final void setPriority(int level) can be used.
Here, level specifies the new priority setting for the calling thread.
The value of level must be within the range MIN_PRIORITY and
MAX_PRIORITY. Currently, these values are 1 and 10, respectively.
To return a thread to default priority, specify NORM_PRIORITY, which is
currently 5.
These priorities are defined as static final variables within Thread.
The current priority setting can be obtained by calling final int getPriority( ) of
Thread.
Most of the inconsistencies arise when you have threads that are relying on
preemptive behavior, instead of cooperatively giving up CPU time.
The safest way to obtain predictable, cross-platform behavior with Java is to use
threads that voluntarily give up control of the CPU.
Synchronization
When two or more threads need access to a shared resource, they need some way
to ensure that the resource will be used by only one thread at a time.
The process by which this is achieved is called synchronization.
class Synch {
public static void main(String args[]) {
Callme target = new Callme();
Caller ob1 = new Caller(target, "Hello");
Caller ob2 = new Caller(target, "Synchronized");
Caller ob3 = new Caller(target, "World");
// wait for threads to end
try {
ob1.t.join();
ob2.t.join();
ob3.t.join();
} catch(InterruptedException e) {
System.out.println("Interrupted");
}
}
}
OUTPUT:
[Synchronized[Hello[World]
]
]
In this program, nothing exists to stop all three threads from calling the same
method, on the same object, at the same time.
This is known as a race condition, because the three threads are racing each other
to complete the method.
Solution is: class Callme {
synchronized void call(String msg) { }.
This prevents other threads from entering call( ) while another thread is using it.
After synchronized has been added to call( ), the output of the program is as
follows:
[Hello]
[Synchronized]
[World]
Any time that you have a method, or group of methods, that manipulates the
internal state of an object in a multithreaded situation, you should use the
synchronized keyword to guard the state from race conditions.
Once a thread enters any synchronized method on an instance, no other thread can
enter any other synchronized method on the same instance.
However, non-synchronized methods on that instance will continue to be callable.
Imagine that you want to synchronize access to objects of a class that was not
designed for multithreaded access. That is, the class does not use synchronized
methods.
Further, this class was not created by you, but by a third party, and you do not
have access to the source code. Thus, you can’t add synchronized to the
appropriate methods within the class.
Polling is usually implemented by a loop that is used to check some condition repeatedly.
Once the condition is true, appropriate action is taken. This wastes CPU time.
For example,
Consider the classic queuing problem, where one thread is producing some data
and another is consuming it.
To make the problem more interesting, suppose that the producer has to wait until
the consumer is finished before it generates more data.
In a polling system, the consumer would waste many CPU cycles while it waited
for the producer to produce.
Once the producer was finished, it would start polling, wasting more CPU cycles
waiting for the consumer to finish, and so on.
Clearly, this situation is undesirable.
OUTPUT:
Put: 1
Got: 1
Got: 1
Got: 1
Got: 1
Got: 1
Put: 2
Put: 3
Put: 4
Put: 5
Put: 6
Put: 7
Got: 7
As you can see, after the producer put 1, the consumer started and got the same 1
five times in a row.
Then, the producer resumed and produced 2 through 7 without letting the
consumer have a chance to consume them.
The proper way to write this program in Java is to use wait( ) and notify( ) to signal in
both directions.
Deadlock
A special type of error that you need to avoid that relates specifically to multitasking
is deadlock, which occurs when two threads have a circular dependency on a pair of
synchronized objects.
Deadlock is a difficult error to debug for two reasons:
1. In general, it occurs only rarely, when the two threads time-slice in just the right
way.
2. It may involve more than two threads and two synchronized objects. (That is,
deadlock can occur through a more convoluted sequence of events than just
described.)
The next example creates two classes, A and B, with methods foo( ) and bar( ),
respectively, which pause briefly before trying to call a method in the other class.
The main class, named Deadlock, creates an A and a B instance, and then starts a
second thread to set up the deadlock condition.
The foo( ) and bar( ) methods use sleep( ) as a way to force the deadlock condition
to occur.
class A {
synchronized void foo(B b) {
String name = Thread.currentThread().getName();
System.out.println(name + " entered A.foo");
try {
Thread.sleep(1000);
} catch (Exception e) { System.out.println("A Interrupted"); }
System.out.println(name + " trying to call B.last()");
b.last();
}
synchronized void last() {
System.out.println("Inside A.last");
} }
class B {
synchronized void bar(A a) {
String name = Thread.currentThread().getName();
System.out.println(name + " entered B.bar");
try {
Thread.sleep(1000);
} catch (Exception e) {System.out.println("B Interrupted"); }
System.out.println(name + " trying to call A.last()");
a.last();
}
synchronized void last() {
System.out.println("Inside A.last");
} }
public class Deadlock implements Runnable {
A a = new A();
B b = new B();
Deadlock() {
Thread.currentThread().setName("MainThread");
Thread t = new Thread(this, "RacingThread");
t.start();
a.foo(b); // get lock on a in this thread.
System.out.println("Back in main thread");
}
Because the program has deadlocked, you need to press ctrl-c to end the program.
You can see a full thread and monitor cache dump by pressing ctrl-break on a PC.
You will see that RacingThread owns the monitor on b, while it is waiting for the
monitor on a.
At the same time, MainThread owns a and is waiting to get b.
This program will never complete.
Suspending, Resuming, and Stopping Threads
The mechanisms to suspend, stop, and resume threads differ between early versions of
Java, such as Java 1.0, and 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.
Although these methods seem to be a perfectly reasonable and convenient approach to
managing the execution of threads, they must not be used for new Java programs.
The suspend( ) method of the Thread class was deprecated by Java2 several years
ago.
This was done because suspend( ) can sometimes cause serious system failures.
Assume that a thread has obtained locks on critical data structures.
If that thread is suspended at that point, those locks are not relinquished.
Other threads that may be waiting for those resources can be deadlocked.
The stop( ) method of the Thread class, too, was deprecated by Java2.
This was done because this method can sometimes cause serious system failures.
Assume that a thread is writing to a critically important data structure and has
completed only part of its changes.
If that thread is stopped at that point, that data structure might be left in a corrupted
state.
The trouble is that stop( ) causes any lock the calling thread holds to be released.
Thus, the corrupted data might be used by another thread that is waiting on the
same lock.
Instead, a thread must be designed so that the run( ) method periodically checks to
determine whether that thread should suspend, resume, or stop its own execution.
Typically, this is accomplished by establishing a flag variable that indicates the
execution state of the thread.
As long as this 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.
If it is set to “stop,” the thread must terminate.
The following example illustrates how the wait( ) and notify( ) methods that are
inherited from Object can be used to control the execution of a thread.
NewThread(String threadname) {
name = threadname;
t = new Thread(this, name);
System.out.println("New thread: " + t);
suspendFlag = false;
t.start(); // Start the thread
}
try {
Thread.sleep(1000);
ob1.mysuspend();
System.out.println("Suspending thread One");
Thread.sleep(1000);
ob1.myresume();
System.out.println("Resuming thread One");
ob2.mysuspend();
System.out.println("Suspending thread Two");
Thread.sleep(1000);
ob2.myresume();
System.out.println("Resuming thread Two");
} catch (InterruptedException e) {
System.out.println("Main thread Interrupted");
}
// wait for threads to finish
try {
System.out.println("Waiting for threads to finish.");
ob1.t.join();
ob2.t.join();
} catch (InterruptedException e) {
System.out.println("Main thread Interrupted");
}
System.out.println("Main thread exiting.");
}
}
Obtaining A Thread’s State
You can obtain the current state of a thread by calling the getState( ) method
defined by Thread.
It is shown here: Thread.State getState( )
It returns a value of type Thread.State that indicates the state of the thread at the
time at which the call was made. State is an enumeration defined by Thread.
(An enumeration is a list of named constants.)
Here are the values that can be returned by getState( ):