The purpose of this laboratory is to:
You have already briefly seen in the lectures what is meant by multiprogramming and multithreading. We will concentrate mainly on the multithreading features of Java in these laboratories. To remind you again the essential features of multithreading :
Java provides two approaches for creating and managing multiple threads. In this lab, we will see the first approach and study it carefully. In the next lab, we will see the limitations of this approach and study a more general and powerful approach.
In the first approach, you can create multiple threads using the java.lang.Thread class. The following steps are necessary to do this. I am assuming that you understand how inheritance works in Java. We will see examples of these steps in the code which I will supply later.
Here is an example program for illustrating the above concepts. A similar program is available in the java tutorial. However, we will modify this example program as we progress through this lab. I will explain this program in details, but only the new features in future programs will be explained in details. You should save this program in a file called Example1.java.
Before we go into the details of this program, let us execute it. If you execute this program, you will get different output in different executions! We should expect this since there are multiple (two in fact) threads in the program and we do not have any control over the order in which these threads gets scheduled and rescheduled in the machine. This is a sample output :
thread1: Java
thread1: is
thread2: Java
thread1: an
thread2: is
thread1: exciting
thread2: an
thread1: new
thread2: exciting
thread1: language
thread1: for
thread1: concurrent
thread2: new
thread1: programming.
thread2: language
thread2: for
thread2: concurrent
thread2: programming.
The purpose of this program is to print the following message on the console : Java is an exciting new language for concurrent programming. Two threads are created in the program and each thread prints this message word by word. Whenever a thread prints a word, it prints its identity and a ':' just before the word. In our sample output, you can see how the control switches back and forth between the two threads. If you execute the program many times, you will see all sorts of interleaving of the words.
Let us now go through this program and try to understand the details.
The program starts as a single thread of execution when the execution of the main(..) method starts. However, two new threads are created immediately after that. So, there are now three threads of control within the program and they all execute independently and simultaneously. You should be able to understand the meaning of the word simultaneously.
Once the two new threads are created, they have all the methods in the class MyThread since they are instances of this class. In addition to that, they inherit all the methods of the Thread class. Also, they share the same address space and hence the array message[]. Now they go on printing the message word by word (and wait in between words) until the array message is completely printed. Also, the control switches back and forth between the two threads (the CPU is scheduled and rescheduled) and we see the different interleaving of the words.
See the different interleavings of the words from the two threads. Experiment with the multiplication factor 1000 in method randomWait(). You may increase it, decrease it or omit it. Note that, as you decrease this factor, your threads will sleep less and there is a chance that a thread will be able to print more words of the message in one shot (in one turn of the CPU). In the extreme case, the words from one thread will be printed first, followed by the words of the other thread. Move on to the next task when you have amused yourself thoroughly.
In this task, we want to print a message on the console whenever a thread dies.
Remember that there are three threads in our program, the thread for the
main(..) method in class Example1 and the two threads
created in the main().
There are several different ways of doing this. But there is a restriction.
The obituary for a particular thread should be printed only once.
There is a method in class Thread called isAlive() which
can be used for checking whether a thread is alive or not. For example, if you
have a line in your program :
thread1.isAlive()
this call will return a boolean value true as long as thread1
is alive and will return false if thread1 is dead.
One approach for printing the obituaries for the two threads we have created
is to use this isAlive() function. (I should admit that this may not
be the best approach). Now of course, a thread itself cannot check whether it
is dead, because if it could it won't be dead in the first place. So, we have to
place this check somewhere else. A good place is the main(..)
method.
Here is my attempt to write obituaries using this idea. Unfortunately, this solution may print an obituary more than once. Save this in a file called Example2.java, execute and see what it does. Correct this solution so that an obituary is printed only once.
In addition to this, try to write a different program for printing obituaries (by modifying Example1.java).
We have discussed in the lectures that the CPU scheduler always schedules processes/threads with higher priorities first. What happens if all the threads have the same priority? Different operating systems have different policies on scheduling equal priority threads. Windows NT schedules equal priority processes in a round robin fashion. However, in Linux, a single thread may monopolise the CPU if all the threads are of equal priority. In Example2.java, you may notice that the first statement of the do loop is Thread.yield(). I have added this for those of you running this program in Linux. The result of the yield() method is that whenever a thread encounters this, it yields the control. I have done this to prevent the thread corresponding to the main() method from monopolising the CPU. If you are working in Linux, delete this line and execute the program and see what happens. The other two threads will never be executed! If you are working in Windows NT, the deletion of this line will have no effect.
Now, we will learn how to set priorities for threads. This is done through the setPriority() method of the Thread class. Each thread starts its life with the priority of the thread which created it. This priority is NORM_PRIORITY or 5 by default, in a scale of 1 to 10. 1 is referred to by a constant MIN_PRIORITY and 10 is called MAX_PRIORITY.
For example, we could set the priority of thread1 by :
thread1.setPriority(8);
thread1.setPriority(Thread.NORM_PRIORITY+3);
thread1.setPriority(Thread.MAX_PRIORITY-2);
thread1.setPriority(Thread.MIN_PRIORITY+7);
and all the four statements will have the same effect.
Set different priorities for the two threads in the program Example1.java. You have to set the priorities just after you create the threads and before you call the start method in main(..). Execute the programs and see the interleavings of words from the two threads. Set the priority of thread1 to 10 (the highest priority) and don't assign any priority to thread2 (NORM_PRIORITY). Execute the program. You may expect that thread1 will print all its words before thread2 gets a chance since thread1 has the highest priority. What do you see in practice? Why? Now, suppress the call to the method randomWait() in method run(). Execute the program. Do you see any changes? Why?
Here is another version of Example1.java with a little modification. Save this in a file Example3.java and try to find out the difference. Execute it and see the output. Can you explain the output now?
Amitava Datta
March, 2000.