So far, we have written many programs. All of them can be termed as single threaded. For last many years, programming languages were producing single-threaded programs. Only recently, multi-threading support has appeared in programming languages. Java is providing an elegant, easy-to-use multi-threading environment to programmers.
Let us first understand what a thread is.
When we start running a program, execution begins with method main. Executing of a method means executing statement after statement in sequence. Please note that loops like for and while are considered as a single (may be complex) statement. As CPUs started becoming faster and faster and OS started supporting multi-tasking, it becomes apparent that we should be able to run more than one method at a time. This brings in the concept of thread. One method (or group of some statements) is put in one envelope and sent for execution. At the same time, another code is sent for execution. The program controls them through some invisible monitoring mechanism. We imagine that these envelopes are tied to the programs by a thread.
Let us not bother how a single processor can execute two codes simultaneously. We will discuss it later.
One of the main motives behind the introduction of threads is to maximize the use of resources. In simple terms, we want to run our programs faster. Consider a case in which we want to run a simple program of searching an array. It involves two tasks: reading data from file and specifying interactively the number N which we want to search.
Consider a pseudo code:
Read data from file;
Get N from console;
It is possible to run these actions in an overlapped manner, thereby saving time.
Let us take another example. Consider a case in which we want to compare bubble sort and quick sort. The methods for the same are available to us. We want both the methods to execute simultaneously. With our present programming knowledge, we can execute these methods one after the other. We cannot run them simultaneously.
This action is possible only when we do it with two threads. This excellent demonstration is available in The Java Tutorial. Readers are advised to see it to appreciate the power of threads. With so much motivation to study threads, let us start with creating and running threads.
There are two ways to create a new thread of execution. One way is to declare a class to be a sub-class of Thread
. This sub-class should override the run method of class Thread
. The other way to create a thread is to declare a class that implements the Runnable
interface. That class then implements the run method.
Let us start with the first method of creating thread. It will require the following steps.
Define a sub-class which extends class Thread.
The code we want to execute has to be placed in a method run()
of this sub-class.
Next, create an object of this sub-class and execute the run method.
For example, a sub-class FirstThread
could be written as follows:
class FirstThread extends Thread
{ // declarations
// constructor if required
public void run ()
{
// The code we want to run comes here.
}
}
Next, the following code can be used to create the thread and set it running.
FirstThread th1 = new FirstThread();
th1.start();
As the concept of thread is quite complex, let us write a small program which runs only one thread. This will help us in getting familiar with a thread.
Problem: Write a program to demonstrate running of a single thread.
Solution: Since we have to run some useful code in a thread, let us take a simple example of sorting. We will sort a small array of integers in the thread (see Program 18.1).
PROGRAM 18.1 A Single Thread
// thr1.java
class FirstThread extends Thread
{ int A[] = {35,7,23,1,14};
public static void sort(int [] A)
{ int i,j,temp;
System.out.println("Initial data");
for(i=0; i<5;i++)
System.out.print(A[i]+" ");
System.out.println();
// now sorting
for(i=0; i<5-1;i++)
for(j=0; j<5-1;j++)
{ temp = A[j];
A[j] = A[j+1];
A[j+1] = temp;
}
System.out.println("data after sorting");
for(i=0; i<5;i++)
System.out.print(A[i]+" ");
}
public void run()
{ sort(A);
}
}
class thr1
{ public static void main(String args[] )
{ System.out.println("<---thr1.java--->");
FirstThread th1 = new FirstThread();
th1.start();
}
};
If we run the program, we will get the following output.
Output
<---thr1.java--->
Initial data
35 7 23 1 14
data after sorting
1 7 14 23 35
You will ask me why is the running of a single thread shown in a chapter of multi-threading? Well, running many threads at a time is no different from running a single thread. And of course, if you cannot run even one thread, you cannot run many.
After studying how a single thread comes into action, we are in a position to study what is called multi-threading, that is many threads in action (simultaneously). We have to start various threads one after the other by calling their start method.
Here, every thread is expected to run a specific code. By the word code, we mean a group of Java statements. We know that most of the time we have a single processor in a computer. Naturally, not all the threads are going to run simultaneously. The Java runtime machine switches execution of threads at short intervals. It gives us the illusion that all the threads are running simultaneously. Analogy can be drawn to a multi-tasking OS like Windows running various tasks simultaneously.
As every good thing has to come to an end, so does the thread. It is born, passes through different phases, and then comes to an end. This brings us to the life cycle of a thread.
A thread in its lifetime goes through various states. Let us study the life cycle of a thread in brief.
When we create a thread naturally, it is in “new” state. The most noticeable state is when the thread is running. We call it the “running” state. If there is any visible action (on screen), we can easily notice it. But as the time comes to run another thread, the current thread must go to another state.
In the next state, the thread is not running, but is ready to run when given a chance. This state is therefore called “runnable” state. (We may also call it “ready” state.)
Sometimes a thread requires some input data. It cannot proceed unless it is made available by the OS. Such a state is called a “blocked” state.
When a thread comes to the end of its code, naturally its life gets over. Hence, it enters a “dead” state.
Any thread starts with the new state and ends up in a dead state. During its life cycle, it moves from state to state. Figure 18.1 shows the state transition diagram of a thread’s life cycle.
Figure 18.1 Life Cycle of a Thread
We will describe these state transitions in the following table.
After studying the life cycle of a thread, now let us write a program with three threads. To make it interesting, let us use graphics.
Problem: Write a program which contains three threads.
Solution: See Program 18.2.
PROGRAM 18.2 Demo of Threads
// thgraph2.java
import java.awt.*;
class BPtimer
{ public static void delay(long n)
{ long t1,t2;
t1 = System.currentTimeMillis();
t1 = t1 + n;
t2= System.currentTimeMillis();
while (t2 < t1) t2= System.currentTimeMillis();
}
}
class VP extends Frame
{ Graphics g1;
VP (int xdim, int ydim) //constructor
{ setSize(xdim,ydim);
setBackground(Color.white);
setVisible(true);
g1=getGraphics();
g1.setColor(Color.blue);
}
class A extends Thread
{ public void run()
{ for (int i = 0; i< 30;i++)
g1.drawLine(100,100,100+i*10,100);
}
g1.drawString(" Exit from A",120,100-30);
}
}
class B extends Thread
{ public void run()
{ for (int j = 0; j< 30;j++)
{ BPtimer.delay(150);
g1.drawLine(100,200,100+j*10,200);
}
g1.drawString(" Exit from B",120,200-30);
}
}
class C extends Thread
{ public void run()
{ for (int k = 0; k< 30;k++)
{ BPtimer.delay(110);
g1.drawLine(100,300,100+k*10,300);
}
g1.drawString(" Exit from C",120,300-30);
}
}
public void show_me()
{ new A().start();
new B().start();
new C().start();
}
}
class thgraph2
{ public static void main(String args[])
{ VP vp1= new VP(500,400);
vp1.show_me();
// System.exit(0);
}
}
If you run this program, you will see all the three lines growing. The last line grows fastest. Thread C exits first. When all threads complete their execution, the following screen appears.
Output
Note the last line marked in bold. It is removed by commenting out. If it is included, we do not see any output. Please note that it is not the last line but one line above that starts the running of the threads. They start running. At that time, if we close the program (actually Java runtime machine), everything gets closed.
Runnable
InterfaceAs stated earlier, Java provides an alternate way to create a thread. We can declare a class that implements the Runnable
interface. In this class, we have to implement the run method. To start a thread, first we have to pass the instance of this class as an argument.
Class SecondThread
implements Runnable
{ // declarations
// constructor if required
public void run()
{
// The code we want to run comes here.
}
}
Now the following code can be used to create a thread and set it running.
SecondThread th2 = new SecondThread();
Thread some_name = new Thread(th2).start();
Please note the above carefully. th2
is the object of type SecondThread
. We create a new Thread
some_name
with this object as a parameter. We call this method as the start for running this thread. This method invokes the run method of object th2
. The above example can be reduced to the following code.
SecondThread th2 = new SecondThread();
new Thread(th2).start();
Let us study it with an example.
Problem: Write a program to demonstrate the use of interface Runnable
.
Solution: Let us rewrite the earlier program (thr1.java
) where we sort a small array of integers in the thread (see Program 18.3).
PROGRAM 18.3 Interface Runnable
// thr2.java
// Demonstrates interface Runnable
class secondThread implements Runnable
{ int A[] = {35,7,23,1,14} ;
public void run()
{ sort(A);
}
public static void sort(int [] A)
{ int i,j,temp;
System.out.println("Initial data");
System.out.print(A[i]+" ");
System.out.println();
// now sorting
for(i=0; i<5-1;i++)
for(j=0; j<5-1;j++)
if( A[j]> A[j+1])
{ temp = A[j];
A[j] = A[j+1];
A[j+1] = temp;
}
System.out.println("data after sorting");
for(i=0; i<5;i++)
System.out.print(A[i]+" ");
}
}
class thr2
{ public static void main(String args[])
{ System.out.println("<---thr2.java--->");
System.out.println("Demonstrates interface Runnable");
secondThread th2 = new secondThread();
new Thread(th2).start();
}
};
If you run the program, you will see the following output.
Output
<---thr2.java--->
Demonstrates interface Runnable
Initial data
35 7 23 1 14
data after sorting
1 7 14 23 35
It is possible to set a priority value to a thread. Java provides three predefined constants for this purpose, as shown in Table 18.1.
Table 18.1 Thread Priority Constants
Constant | Meaning |
---|---|
MAX_PRIORITY |
The maximum priority that a thread can have. |
MIN_PRIORITY |
The minimum priority that a thread can have. |
NORM_PRIORITY |
The default priority that is assigned to a thread. |
Please note that Java depends on thread scheduling policy of the OS, which is discussed further in the text for Windows XP. It uses the “preemptive thread scheduling” policy. When a higher priority thread becomes ready for execution, currently running lesser priority thread is stopped. A lower priority thread cannot preempt higher priority running thread. It has to wait until the running thread either completes or goes to blocking state for some reason.
If there are many threads with same priority, they are given an opportunity to run in round robin fashion.
Let us study a simple program to study this concept.
Problem: Write a program to demonstrate thread priority.
Solution: See Program 18.4.
PROGRAM 18.4 Thread Priority
// thread2.java
// thread priority
class BPtimer
{ public static void delay(long n)
{ long t1,t2;
t1 = System.currentTimeMillis();
t1 = t1 + n;
t2= System.currentTimeMillis();
while (t2 < t1) t2= System.currentTimeMillis();
}
}
class A extends Thread
{ public void run()
{ for (int i = 1; i< 5;i++)
{ BPtimer.delay(500);
System.out.println(“== from thread A = ” + i);
}
System.out.println(“ Exit from A”);
}
}
class B extends Thread
{ public void run()
{ for (int j = 1; j< 5;j++)
{ BPtimer.delay(500);
System.out.println(“------ from thread B =” + j);
}
System.out.println(“ Exit from B”);
}
}
class C extends Thread
{ public void run()
{ for (int k = 1; k< 5;k++)
{ BPtimer.delay(500);
System.out.println(“------ from thread C = “ + k);
}
System.out.println(“ Exit from C”);
}
}
{ public static void main(String args[])
{ System.out.println(“<---thread2.java--->”);
A th1= new A();
th1.setPriority(Thread.MIN_PRIORITY);
th1.start();
B th2 = new B();
th2.setPriority(Thread.NORM_PRIORITY);
th2.start();
C th3 = new C();
th3.setPriority(Thread.MAX_PRIORITY);
th3.start();
}
}
If you run the program, you will see the following output.
Output
<---thread2.java--->
********* from thread C = 1
********* from thread C = 2
********* from thread C = 3
********* from thread C = 4
Exit from C
------ from thread B =1
------ from thread B =2
------ from thread B =3
------ from thread B =4
Exit from B
== from thread A = 1
== from thread A = 2
== from thread A = 3
== from thread A = 4
Exit from A
Let us modify our previous graphics program.
Problem: Write a program to demonstrate thread priority using graphics.
Solution: See Program 18.5.
PROGRAM 18.5 Threads Demo II
// th3.java
import java.awt.* ;
import javax.swing.* ;
class BPtimer
{ public static void delay(long n)
{ long t1,t2;
t1 = System.currentTimeMillis();
t1 = t1 + n;
t2= System.currentTimeMillis();
while (t2 < t1) t2= System.currentTimeMillis();
}
}
class VP extends JFrame
{ Graphics g1;
VP (int xdim, int ydim) //constructor
{ setSize(xdim,ydim);
setBackground(Color.white);
setVisible(true);
g1=getGraphics();
g1.setColor(Color.blue);
}
class A extends Thread
{ public void run()
{ for (int i = 0; i< 30;i++)
{ BPtimer.delay(150);
g1.drawLine(100,100,100+i*10,100);
}
g1.drawString(" Exit from A",120,100-30);
}
}
class B extends Thread
{ public void run()
{ for (int j = 0; j< 30;j++)
{ BPtimer.delay(250);
g1.drawLine(100,200,100+j*10,200);
}
g1.drawString(" Exit from B",120,200-30);
}
}
class C extends Thread
{ public void run()
{ for (int k = 0; k< 30 ;k++)
{ BPtimer.delay(100);
g1.drawLine(100,300,100+k*10,300);
}
g1.drawString(" Exit from C",120,300-30);
}
}
public void show_me()
{ A th1 = new A();
th1.setPriority(Thread.NORM_PRIORITY);
th1.start();
System.out.println(th1.toString());
B th2 = new B();
th2.setPriority(Thread.MAX_PRIORITY);
th2.start();
System.out.println(th2.toString());
C th3 = new C();
th3.setPriority(Thread.NORM_PRIORITY);
th3.start();
System.out.println(th3.toString());
}
}
public class th3
{ public static void main(String args[])
{ VP vp1= new VP(500,400);
vp1.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// vp1.setVisible(true);
BPtimer.delay(150); // note 1
vp1.show_me();
//System.exit(0);
}
}
If you run the program, at the end you will see the following output.
Output
We have to watch this application while running. As Thread B is set to maximum priority, it runs and runs. It finishes before others could put even first step. Afterwards, both threads A and C start moving. As the priority of both C and A is the same, both run together.
There are many points to note in Program 18.5. Basically, code is repeated from thgraph2.java
. However, delay in thread B is more that delay in A and delay in C. Still it starts as well as finishes even before others two could be visible. Delay in C is less than that of A. Hence, it runs faster than thread A. Lastly, we have added one more statement.
BPtimer.delay(150); // note 1
You may wonder why a delay is called at this place. Strictly speaking, this is not necessary. When we run the program without it, at times thread B started running even before the frame could become properly visible. When the frame becomes visible, whatever has been drawn before vanishes. This delay helps in getting proper output. Alternatively, we may add
vp1.setVisible(true);
to achieve the same result.
There are two types of threads in the system. The first one is called “Daemon thread”. The second type is normal or non-Daemon thread. In a program, many threads run. Eventually they die. Whenever all normal threads finish (terminate), the program comes to an end. The program does not wait for Daemon thread to terminate. Can we declare a thread as Daemon thread? Find the answer for yourself.
When we write multi-threaded programs, we may not imagine that there may be complications in the programs. Those of you who have studied the subject of “Operating System” are aware that there are problems like deadlocks. Without going into details, we may say that if two threads are using common data, there is a possibility of erroneous behaviour in the program. Let us take an example of standard producer–consumer problem.
A thread1
may be producing some data and thread2
may be using or consuming that data. If there is no proper synchronization between the two, there will be an arbitrary result.
Let us say that the producer is adding money in the basket and the consumer is picking it up. Both are acting with random time delay. If the producer adds to the basket when the consumer is picking it up, after a while the contents of the basket will be unpredictable. If we run the program for 1 hour, at the end the consumer may pick up more than what producer had produced.
To avoid this, we have to use what is known as synchronizing tools. Java provides many tools for synchronization. Let us study them one by one.
Let us again consider a producer–consumer problem. Both producer and consumer use object of class basket
to put or get data. Let us assume we have method put_or_get(){…}
in class basket
. Let us define this method with keyword synchronised as follows:
class basket
{ synchronised put_or_get()
{
// method body .
}
}
Now assume that both consumer and producer processes are running from different threads. If both of them want to use put_or_get()
method simultaneously, the system will not allow them to do so. One of the threads has to wait until other thread finishes. This will solve any concurrency-related problem.
Synchronized statements offer another way to create synchronized code. A word synchronized placed before a block makes the block synchronized. Synchronized statements must specify the object that provides the intrinsic lock. Consider the following example. Here instead of a method, a statement (block) is synchronized
.
public void someName()
{ synchronized (this) {
// statements
}
In the previous paragraph, we talked of intrinsic lock. In Java, the whole process of Synchronization is based on the concept of intrinsic lock or monitor lock. We may simply call it as “monitor”.
Every object has an intrinsic lock associated with it. For proper working every thread must acquire this lock (monitor) before using object fields. The thread should release the monitor when it has finished its work. During this time, we can say that this thread owns the monitor and no other thread can acquire the same lock.
wait(), notify()
, and notifyAll()
We can use inter-thread communication for proper coordination among the threads. A thread may sleep until it is notified by the other thread. The “Object
class” provides methods wait, notify
, and notifyAll
for this purpose.
Method wait()
makes the thread to wait for getting the monitor. Since it is possible that this thread may be interrupted, it has to be used with try–catch
block.
Method notify()
wakes up a single thread that is waiting on this object’s monitor. Method notifyAll()
wakes up all threads that are waiting on this object’s monitor.
It is important to note that all these methods must be called only when they own the monitor of that object. Otherwise, they throw IllegalMonitorStateException
.
A thread becomes the owner of the object’s monitor by executing a synchronized instance method of that object. Alternately, it can do so by executing a synchronized statement which specifies that object.
join()
Java offers method join()
in class Thread. It is quite useful in co-coordinating Threads. This method makes the current thread to wait until other specified thread terminates. Let us see this in action in the following program.
Problem: Write a program to demonstrate the use of method join()
.
Solution: Let us continue our example of drawing horizontal lines. Thread “A” draws the line undisturbed. In thread “B” we will draw only first 15 units. Then we will call join on thread “A”. This will suspend thread B until thread “A” ends. After that thread, “B” resumes and draws the remaining line.
PROGRAM 18.6 Demo of Method Join()
// th5.java
import java.awt.* ;
import javax.swing.*;
class VP extends JFrame
{ Graphics g1;
VP (int xdim, int ydim) //constructor
{ setSize(xdim,ydim);
setBackground(Color.white);
setVisible(true);
g1=getGraphics();
g1.setColor(Color.blue);
}
class A extends Thread
{ public void run()
{ for (int i = 0; i< 30;i++)
{ try
{ sleep(250);}
catch(Exception e){ };
g1.drawLine(100,100,100+i*10,100);
}
g1.drawString(" Exit from A",120,100-30);
}
}
static A th1;
static B th2;
class B extends Thread
{ public void run()
{
for (int j = 0; j< 15;j++)
{ try
{ sleep(200);}
catch(Exception e){ };
g1.drawLine(100,200,100+j*10,200);
}
g1.drawString(" waiting for A to finish",120,200-20);
try
{ th1.join() ;}
catch(Exception e){ };
g1.drawString(" Resuming operation",120,200+20);
for (int j = 16; j< 30;j++)
{ try
{ sleep(250);}
catch(Exception e){ };
g1.drawLine(100,200,100+j*10,200);
}
g1.drawString(" Exit from B",120,200+40);
}
}
// static B th2;
public void show_me()
{ th1 = new A();
th1.setPriority(Thread.NORM_PRIORITY);
th1.start();
th2 = new B();
th2.setPriority(Thread.NORM_PRIORITY);
th2.start();
}
}
class th5
{ public static void main(String args[])
{ VP vp1= new VP(500,400);
vp1.setTitle("Demo Of join() ");
vp1.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
vp1.setVisible(true);
vp1.show_me();
}
}
When we run Program 18.6, we first see both threads A and B start drawing corresponding lines. Midway we see that thread B halts with the message “waiting for A to finish”. Thread A continues and finishes operation. Now thread B resumes with message “Resuming operation”. It continues until it completes its drawing task. It exits with the message “Exit from B”. The following figure shows the screenshot at the end of the program.
Remember the famous tale of hare and tortoise? You may imagine thread A as tortoise. Thread B represents the hare which sleeps in between. It wakes up only when the tortoise wins the race.
Consider a case of reading an array from a file and sorting it. There is no way of sorting an array unless it is fully read. If the two actions of reading and sorting are in different threads, sorting can begin only when reading is complete, if we use the method join()
.
Timer
and TimerTask
The package Java.util
provides two important classes namely Timer
and TimerTask
. These are normally used in conjunction. Java offers the facility of running a thread in background. This thread schedules tasks for future execution. It can run thread once or repeatedly at regular intervals.
It goes without saying that tasks so scheduled should finish quickly. Also note that that there is no absolute guarantee that the task will be scheduled at specified time.
Let us study these classes with the help of an example.
Problem: Write a program to demonstrate Timer
and TimerTask
classes.
Solution: In this example, we will create a Timer
object say timea
. We want the program to run for say 5 seconds. We will put the main thread to sleep for 5,000 milliseconds. During this time, TimerTask
will execute every 1000 milliseconds. In the timer Task
, we will print word hello
(see Program 18.7).
PROGRAM 18.7 TimerTask
// timer1.java
import java.util.*;
public class timer1
{ public static void main(String args[])
{ System.out.println("<---timer1.java--->");
Timer timea = new Timer(true);
timea.scheduleAtFixedRate(new MyTask(), 0, 1000);
try
{ Thread t = Thread.currentThread();
t.sleep(5000);
} catch (Exception e){};
}
}
class MyTask extends TimerTask
{
public void run()
{
System.out.println("Hello");
}
}
If you run the above program, you will get the following output.
Output
<---timer1.java--->
Hello
Hello
Hello
Hello
Hello
Please note that the word hello is printed six times. One expects it to print only five times. Change the sleep time by few milliseconds, less than 5,000 milliseconds, and you will get the word printed only five times.
You will ask me, why force main thread to sleep? The answer is that otherwise the program comes to an end immediately. The action we want to watch is guaranteed by the timer. We have to ensure that the program runs for a finite time.
Table 18.2 summarizes the constructors from class Timer.
Table 18.2 Constructor Summary: Class Timer
Timer() |
Creates a new timer. |
Timer(boolean isDaemon) |
Creates a new timer whose associated thread may be specified to run as a daemon. |
Timer(String name) |
Creates a new timer whose associated thread has the specified name. |
Timer(String name, boolean isDaemon) |
Creates a new timer whose associated thread has the specified name and may be specified to run as a daemon. |
Table 18.3 summarizes the methods from class Timer.
Table 18.3 Method Summary: Class Timer
Table 18.4 summarizes the methods from class TimerTask.
Table 18.4 Methods Summary: Class TimerTask
boolean cancel() |
Cancels this timer task. |
abstract void run() |
The action to be performed by this timer task. |
long scheduledExecutionTime() |
Returns the scheduled execution time of the most recent actual execution of this task. |
Table 18.5 summarizes the constructors from class Thread.
Table 18.5 Constructor Summary: Class Thread
Table 18.6 summarizes the methods from class Thread.
Table 18.6 Methods Summary: Class Thread
blocked state, Daemon thread, dead state, getName()
, getPriority()
, interrupt()
, isAlive()
, isDaemon()
, join()
, MAX_PRIORITY
, MIN_PRIORITY
, NORM_PRIORITY
, preemptive thread scheduling, ready state, run, Runnable
, running state, start()
, sleep()
, synchronized, thread, thread life cycle, thread priority, Timer
, TimerTask
, yield()
NORM_PRIORITY |
NORM_PRIORITY is the default priority of a newly created thread. |
Thread priority | Threads may yield to threads of lower priority. |
Thread simultaneous access | It is possible for two threads to access the same variable or the same method of the same object at the same time. However, the net result will be unpredictable. |
Thread class | By using a thread class, multi-threaded program can be created. |
Synchronized | Every object that has synchronized methods has a monitor. |
Monitor | The sleep() method tells the calling thread to give up monitor until some other thread enters the same monitor. |
Preemptive switching | A thread priority is used to decide when to move from one running thread to the next. This is called preemptive switching. |
sleep() |
The sleep() method is used to temporarily stop the execution of the thread. |
join() |
The join() method suspends the current thread until that thread object dies. |
start() |
After the start() method is called, the thread is in a Runnable state. |
Let t1 print message ping-->
and t2 print message <--pong
. Take as command line arguments the following inputs to the program:
Sleep Interval for thread t1
Sleep Interval for thread t2
Messages per cycle
No. of cycles
Runnable
interface.18.118.20.231