Java Programming, Lecture Notes # 58, Revised 10/06/99.
Preface
Introduction
Overview
Technical Details
Review
Students in Prof. Baldwin's Introductory Java Programming classes at ACC are responsible for knowing and understanding all of the material in this lesson (except that they are not responsible for detailed information that is specific to C++).
The detailed material on C++ is provided as supplementary material for the benefit of persons already familiar with C++ who are making the transition into Java.
According to The Java Handbook, by Patrick Naughton, one of the original members of the Java design team at Sun,
"Multi-threaded programming is a conceptual paradigm for programming where you divide programs into two or more processes which can be run in parallel." |
Because many students in the class will not be familiar with the concept, or even worse, may have it confused with "multi-processing", we will begin this lesson with a non-technical overview discussion. Following the overview, we will launch into the technical details.
NOTICE: The following is the partial text of a message received from a reader on 3/6/98 giving us a "heads-up" on what to expect in the future of threads. Thanks go out to this reader for the information: The following link: http://java.sun.com/docs/books/tutorial/post1.0/preview/threads.html explains why, and how to write better Java programs without these keywords. You might want to mention it in your discussion on Threads. Overall, this is the best discussion of Java I've seen on-line and all my books are hopelessly out of date. Keep up the good work. |
Multithreading and Multiprocessing, the Difference
Why Do We Need Multithreading
Two Ways to Thread
Stopping Threads
Daemon Threads
Synchronization
Thread Priority
Sharing Resources at the Same Priority
A thread is short for thread of control. We will also learn some other names as we go along.
If you have spent any time working with a web browser, you have been using threads without even knowing it.
Web browsers typically use different threads running in parallel to accomplish a variety of tasks, "apparently" concurrently.
For example, on many web pages, you can begin scrolling the page and reading the text before all the images are available for display. In this case, the browser would be downloading an image in one thread and supporting your scrolling activity in another thread.
C and C++ do not support threading as an inherent part of the language. If you need to write a threaded program in one of these languages, you must either deal with features of the operating system, or perhaps install a set of class libraries designed specifically to support threading for a particular platform. |
Threading is supported directly by the Java language, and as such provides the ability to write threaded programs which are platform independent (unfortunately, with some variations in behavior between platforms).
Most books have a page or more devoted to this topic. For an interesting and somewhat humorous discussion of the topic, see the Java Primer Plus by Tyma, Torok, and Downing.
In a nutshell, multiprocessing refers to two or more programs executing, "apparently" concurrently, under control of the operating system. The programs need have no relationship with each other, other than the fact that you want to start and run them all concurrently. |
Multiprocessing is sometimes referred to as a heavyweight process, possibly because the challenges involved in making it all work are heavyweight challenges.
Multithreading refers to two or more tasks executing, "apparently" concurrently, within a single program. This is often referred to as a lightweight process, possibly because the challenges involved in making it all work are, -- well, you get the picture. |
In both cases, I used the terminology "apparently" concurrently, because if the platform has a single CPU, the processes are not really executing concurrently. Rather, they are sharing the CPU on some basis. However, on platforms with multiple CPUs, it is possible that the processes might actually be executing concurrently.
In both cases, the behavior of the operating system normally gives the illusion of concurrent execution.
Even more important, multithreading can produce programs that accomplish more work in the same amount of time due to the fact that the CPU is being shared between tasks.
Multiprocessing is implemented within the operating system. Any two or more programs capable of executing on the platform can be executed on a multiprocessing basis with no requirement for planning in advance by the authors of the programs.
However, although multithreading definitely requires support from the operating system, in the final analysis, multithreading is implemented within the program. Furthermore, advance planning is required on the part of the author of the program, because it must be specifically designed to run in a multithreaded manner.
And finally, two or more multithreaded programs can be concurrently executing in a multiprocessing system. Therefore, multithreading and multiprocessing can exist concurrently with one another.
Most books have a page or more devoted to this topic also. Again, in a nutshell, many types of programs can make better use of available machine resources by separating tasks into concurrently executing threads.
Some tasks experience long periods while nothing useful is being done, while other tasks constantly need all the resource that they can get.
Suppose, for example, we needed a program that could read 1000 numbers from the keyboard, compute the square root of each number to 100 decimal places, and write the results out to a disk file.
Most keyboard input activities consume very little computer resource. In a strictly sequential program, which is the norm in Pascal, C, and C++, the computer would spend a very large percentage of the time waiting for the keyboard operator to press the Enter key, and would spend relatively little time actually extracting data from the keyboard buffer.
Similarly, but not nearly as severe, a program that needs to write data to a disk file experiences wasted time while the operating system locates the file, positions the write head, etc., before any data actually gets transferred.
Thus, much in the way of computer resource is wasted during keyboard (and other) input/output operations.
I have never attempted to write a program that can compute the square root of a number out to 100 decimal places. However, I am fairly confident that it would consume all the computer resource that it can garner, at least in short bursts.
Therefore, if we were to write our program so that
the computational thread could gain access to most of the computer resources while the other two threads were blocked awaiting communication with the keyboard and the disk controller.
The term blocked means that the thread is waiting for something to happen and is not consuming computer resources. |
Sequential programs do not have this advantage. While it is possible to write sequential programs that cycle in tight control loops, testing various conditions and queuing up tasks, that is probably a less efficient approach than threading.
Obviously, with threading, all that we have accomplished is to move that looping process from the application program to the operating system, but we can hope that the system programmers can do a better job than the rest of us in implementing such a process.
Whether it is better or not, it only has to be programmed once when the operating system is developed, rather than being reprogrammed at varying levels of efficiency in every new application program.
Implement the Runnable Interface
Extend the Thread Class
Another Approach which Extends the Thread Class
In Java, there are two ways to create threaded programs:
I am going to start out by showing you a very simple example of both approaches.
Some authors suggest that implementing the Runnable interface is the most "object oriented" of the two. Whether or not that is true, it is an approach which apparently can be used in all cases, while the approach which extends the Thread class cannot be used in some situations (which I will explain later).
All that is necessary to spawn a thread in Java is to
The code to accomplish the desired objective (do the work) of the thread is placed in the run() method, or in other methods invoked by the run() method. |
One way (which will be the topic of a later section) to instantiate an object of the Thread class is to
Then override the run() method of the Thread class in your new class.
Important: To start the thread actually running, you do not invoke the run() method. Rather, you invoke the start() method on your object. |
However, sometimes it is not possible to extend the Thread class, because you must extend some other class. Remember that Java does not support multiple inheritance.
In programming applets, for example, you must extend the Applet class.
In those cases where it is not possible to extend the Thread class, you can design your class to extend some required class, and also to implement the Runnable interface.
So, the second way (which is the topic of this section) to instantiate an object of the Thread class is to
As before, you would override the run() method of the Thread class in your new class.
This approach is illustrated in the following sample program.
/*File Thread01.java Copyright 1997, R.G.Baldwin Illustrates instantiation and running of threads using the runnable interface instead of extending the Thread class. Tested using JDK 1.1.3 under Win95. The output is: Thread[threadA,5,main] Thread[threadB,5,main] Thread[main,5,main] **********************************************************/ class Thread01{ static public void main(String[] args){ //Instantiate two new thread objects Thread myThreadA = new Thread(new MyThread(),"threadA"); Thread myThreadB = new Thread(new MyThread(),"threadB"); //Start them running myThreadA.start(); myThreadB.start(); try{ //delay for one second Thread.currentThread().sleep(1000); }catch(InterruptedException e){} //Display info about the main thread System.out.println(Thread.currentThread()); }//end main }//end class Thread01 //=======================================================// class DoNothing{ //This class exists only to be inherited by the MyThread // class to prevent that class from being able to inherit // the Thread class, thus requiring it to implement the // Runnable interface instead. }//end class DoNothing //=======================================================// class MyThread extends DoNothing implements Runnable{ public void run(){ //Display info about this particular thread System.out.println(Thread.currentThread()); }//end run }//end class MyThread |
As you can see, this program defines a new class named MyThread which extends a class named DoNothing and implements the Runnable interface.
We override the run() method in the class named MyThread to display information about the thread implemented by an object of the MyThread class.
The only reason for extending DoNothing in this simple example is to consume the one and only inheritance path available to a Java class. The purpose is to simulate a situation where you must extend some other class. Extending another class is not a requirement, and this approach works equally well when you don't extend any other class.
Code in main() instantiates two objects of type Thread using one of several available Thread constructors as shown below.
Thread myThreadA = new Thread(new MyThread(),"threadA"); |
This particular constructor requires two parameters. The first parameter is an object of any class that implements the Runnable interface.
The second parameter is a string which specifies a name for the thread. (Note that the name given to the thread is independent of the name of the reference variable which references the Thread object.)
Various methods are available which allow for manipulation of the thread status by referring to it by name.
Additional code in main() starts the two threads running by invoking the start() method on each of the thread objects. The threads stop and the program terminates when the threads have nothing more to do (this was not the case in JDK 1.0.2.).
Another thing to notice is that main is itself a thread which is started by the interpreter.
Code in the run() method causes the names of each of the threads (along with some other information) to be displayed for both of the two threads instantiated and started within the program.
Code in main() causes similar information to be displayed for the main thread as well.
The sleep() method is invoked on the main thread to implement a one-second delay. The sleep() method and a number of other Thread methods will be discussed in later sections.
This program illustrates the first of two alternative approaches to instantiate and run threads in a Java program. This is the approach which must be used whenever the class being used to instantiate the thread object is required to extend some other class.
As mentioned above, this approach can also be used in those cases where it is not necessary to extend some other class just as well. This is the most general of the two approaches being discussed.
The next approach can be used when the class being used to instantiate the thread object is not required to extend some other class, and therefore, can extend the Thread class.
Let's look at essentially the same simple program which extends (inherits from) the Thread class rather than to implement the Runnable interface.
/*File Thread02.java Copyright 1997, R.G.Baldwin Illustrates instantiation and running of threads by extending the Thread class instead of implementing the Runnable class as was the case in the program named Thread01.java Tested using JDK 1.1.3 under Win95. The output is: Thread[threadA,5,main] Thread[threadB,5,main] Thread[main,5,main] **********************************************************/ class Thread02{ static public void main(String[] args){ //Instantiate two new thread objects Thread myThreadA = new Thread(new MyThread(),"threadA"); Thread myThreadB = new Thread(new MyThread(),"threadB"); //Start them running myThreadA.start(); myThreadB.start(); try{//delay for one second Thread.currentThread().sleep(1000); }catch(InterruptedException e){} //Display info about the main thread System.out.println(Thread.currentThread()); }//end main }//end class Thread02 //=======================================================// class MyThread extends Thread{ public void run(){ //Display info about this particular thread System.out.println(Thread.currentThread()); }//end run }//end class MyThread |
With this approach, the new class named MyThreadextends the Thread class and does not implement the Runnable interface directly. (The Thread class implements Runnable so that MyClass actually implements Runnable indirectly.)
Otherwise, this program behaves essentially the same as the previous program.
The following program also extends the Thread class but is somewhat simpler than the previous program. See if you can spot the difference.
Hint: Look closely at the names of the individual threads and notice that they have default names. This program uses a different version of the Thread constructor which doesn't require any arguments.
Thus, the statements to instantiate the Thread objects are a little less complex. Otherwise, it is essentially the same as the previous program. This is the syntax that you will most often see in Java books and articles which discuss multithreading.
/*File Thread06.java Copyright 1997, R.G.Baldwin Illustrates another way to instantiate and run threads by extending the Thread class instead of implementing the Runnable class as was the case in the program named Thread01.java Tested using JDK 1.1.3 under Win95. The output is: Thread[Thread-1,5,main] Thread[Thread-2,5,main] Thread[main,5,main] **********************************************************/ class Thread06{ static public void main(String[] args){ //Instantiate two new thread objects Thread myThreadA = new MyThread(); Thread myThreadB = new MyThread(); //Start them running myThreadA.start(); myThreadB.start(); try{//delay for one second Thread.currentThread().sleep(1000); }catch(InterruptedException e){} //Display info about the main thread System.out.println(Thread.currentThread()); }//end main() }//end class Thread06 //=======================================================// class MyThread extends Thread{ public void run(){ //Display info about this particular thread System.out.println(Thread.currentThread()); }//end run }//end class MyThread |
.
As mentioned earlier, the threads stop and the program terminates when all of the threads have nothing else to do. This was not the case in JDK 1.0.2, and it was sometimes necessary to invoke the stop() methods to cause the threads to stop.
According to Java Primer Plus, if you set a thread as a daemon thread using setDaemon() method, you are specifying that the thread belongs to the system and not to the process that spawned it.
Daemon threads are useful if you want a thread to run in the background for extended periods of time.
According to Deitel and Deitel, Java, How to Program, "A daemon thread is a thread that runs for the benefit of other threads."
Daemon threads run in the background and do not prevent a program from terminating. For example, the garbage collector is a daemon thread.
A program can include a mixture of daemon and non-daemon threads. The Java virtual machine will exit when only daemon threads remain in a program.
Once you start two or more threads running, unless you specify otherwise, they run asynchronously and independently of one another.
However, what if two or more of the threads share some resource such as an array containing data. You must keep one thread from corrupting the data while it is being processed by another thread.
This is known as synchronization, and we will discuss how to do it in detail with examples.
Java allows you to specify the priority of each thread relative to other threads. Those threads having higher priority get greater access to available resources when such resources are needed.
Some authors indicate that this is one area that is not totally platform independent, in that some platforms provide a greater degree of control over relative priority than other platforms.
Not all implementations of the JVM behave the same with respect to how two or more threads at the same priority level share available resources.
Some implementations automatically time slice the available resources making certain that all threads at the same priority get their fair share of available resources.
Other implementations will allow one thread to hog all the resources until it no longer needs resources.
Your program can cause a thread to purposely yield() resources to other threads at the same priority level.
A thread can be suspended during which time it consumes no resources, and can later resume its activities.
Synchronized methods can implement a form of communication based on wait() and notify() or notifyAll().
These methods are commonly used to coordinate the activities of threads which share a common resource. In many such cases, some threads are producers of data and other threads are consumers.
Note that wait(), notify(), and notifyAll() are not methods of the Thread class, but rather are methods of the Object class.
Now that you have an overview of how it all fits together, let's get into the details.
A Slightly More Substantive Sample Program
Thread Attributes
Where the Action Is, the run Method
Thread State
Threads that Apparently Refuse to Die
IllegalThreadStateException
The isAlive Method
Thread Priority
Daemon Threads
Thread Group
Synchronizing Threads
Deadlock
The Java Tutorial, by Campione and Walrath defines a thread as follows:
"A thread -- sometimes known as an execution context or a lightweight process -- is a single sequential flow of control within a process." |
.
Let's modify one of the previous sample programs to cause the threads to do something a little more substantive so that you can actually see some evidence of concurrent operation.
/*File Thread03.java Copyright 1997, R.G.Baldwin Illustrates instantiation and running of threads using the runnable interface. Each of two threads counts from zero to four and displays the count, sleeping a random amount of time between counts. Tested using JDK 1.1.3 under Win95. The output is: Thread[thrdA,5,main] cnt is 0 Thread[threadB,5,main] cnt is 0 Thread[threadB,5,main] cnt is 1 Thread[threadB,5,main] cnt is 2 Thread[thrdA,5,main] cnt is 1 Thread[thrdA,5,main] cnt is 2 Thread[threadB,5,main] cnt is 3 Thread[thrdA,5,main] cnt is 3 Thread[threadB,5,main] cnt is 4 Thread[thrdA,5,main] cnt is 4 **********************************************************/ class Thread03{ static public void main(String[] args){ //Instantiate two new thread objects with names of // different lengths which helps when viewing the // output. Thread threadA = new Thread(new MyThread(),"thrdA"); Thread threadB = new Thread(new MyThread(),"threadB"); //Start them running threadA.start(); threadB.start(); try{//delay for one second Thread.currentThread().sleep(1000); }catch(InterruptedException e){} }//end main }//end class Thread03 //=======================================================// class MyThread implements Runnable{ public void run(){ for(int cnt = 0; cnt < 5; cnt++){ System.out.println(Thread.currentThread() + " cnt is " + cnt); try{//delay a random amount of time Thread.currentThread().sleep( (int)(Math.random() * 100)); }catch(InterruptedException e){} }//end for-loop }//end run }//end class MyThread |
This program instantiates two independent Thread objects and starts them running asynchronously.
Each thread counts from zero to four and displays the count along with its name for each count.
Each thread also sleeps a random amount of time between counts. Because of the use of a random sleep time, the output from every run will be different. The output from one run is shown in the comments at the beginning of the program.
You can see how each thread
independently of the activities of the other thread (except that they must share available resources when both are awake).
The output from each thread is intermingled with the output from the other. This is because the two threads are running concurrently and asynchronously.
Both run() methods are
A Java program can have many threads, and those threads can run concurrently, either asynchronously or synchronously.
As explained earlier, Java threads are objects of the Thread class, which is part of the java.lang package.
The Thread class implements a platform independent definition of Java threads.
However, the actual implementation of concurrent operation is provided by a system-specific implementation built into the Java Virtual Machine for a specific platform.
For our purposes, the underlying implementation doesn't matter. We will ignore the underlying implementation and program at the level of the thread API.
When you implement a threaded program, you will override the run() method of the Thread class and build the functionality of your threaded program into the run() method.
Of course, the run() method can invoke other methods.
You will start a thread running by invoking the start() method on your Thread object.
The start() method invokes the run() method (and takes care of a few other necessary tasks in the background as well).
Notice that neither of these methods take any parameters.
You are not restricted to the use of a single run() method in a threaded program.
You can define a variety of classes in your program which either extendThread or implement Runnable. You can instantiate multiple Thread objects from each of these classes.
Each of these classes has its own overridden run() method which is independent of the run() methods in the other classes.
The following program upgrades one of the previous programs slightly by defining two different classes from which Thread objects are instantiated.
Each class has its own run() method which is different from the run() method in the other class (although they do similar things in order to make it easier to view the results).
Two Thread objects of each class are instantiated and started. As before, they run asynchronously relative to one another, and the output that they produce is intermingled on the screen.
/*File Thread04.java Copyright 1997, R.G.Baldwin Illustrates instantiation and running of threads using the runnable interface where two different classes are defined each of which uses the Runnable interface. The run() method in one class is different from the run() method in the other class. Two Thread objects are instantiated and started for each of the two thread classes. Each of the threads counts from zero to four and displays the count, sleeping a random amount of time between counts. The format of the display is different between the two thread classes. Tested using JDK 1.1.3 under Win95. The output is for one particular run was: Thread[thrdA,5,main] cnt is 0 Thread[threadB,5,main] cnt is 0 The actual cnt is 0 Thread[Xthrd,5,main] The actual cnt is 0 Thread[Ythread,5,main] Thread[thrdA,5,main] cnt is 1 Thread[threadB,5,main] cnt is 1 Thread[thrdA,5,main] cnt is 2 The actual cnt is 1 Thread[Ythread,5,main] The actual cnt is 1 Thread[Xthrd,5,main] Thread[threadB,5,main] cnt is 2 The actual cnt is 2 Thread[Ythread,5,main] Thread[thrdA,5,main] cnt is 3 The actual cnt is 3 Thread[Ythread,5,main] Thread[thrdA,5,main] cnt is 4 The actual cnt is 2 Thread[Xthrd,5,main] The actual cnt is 4 Thread[Ythread,5,main] Thread[threadB,5,main] cnt is 3 The actual cnt is 3 Thread[Xthrd,5,main] The actual cnt is 4 Thread[Xthrd,5,main] Thread[threadB,5,main] cnt is 4 **********************************************************/ class Thread04{ static public void main(String[] args){ //Instantiate two new thread objects of one type Thread threadA = new Thread( new OneThreadClass(),"thrdA"); Thread threadB = new Thread( new OneThreadClass(),"threadB"); //Instantiate two new thread objects on another type Thread Xthread = new Thread( new AnotherThreadClass(),"Xthrd"); Thread Ythread = new Thread( new AnotherThreadClass(),"Ythread"); //Start them running threadA.start(); threadB.start(); Xthread.start(); Ythread.start(); try{//delay for one second Thread.currentThread().sleep(1000); }catch(InterruptedException e){} }//end main }//end class Thread04 //=======================================================// class OneThreadClass implements Runnable{ public void run(){ for(int cnt = 0; cnt < 5; cnt++){ System.out.println(Thread.currentThread() + " cnt is " + cnt); try{//delay a random amount of time Thread.currentThread().sleep( (int)(Math.random() * 100)); }catch(InterruptedException e){} }//end for-loop }//end run }//end class OneThreadClass //=======================================================// class AnotherThreadClass implements Runnable{ public void run(){ for(int cnt = 0; cnt < 5; cnt++){ System.out.println("The actual cnt is " + cnt + " " + Thread.currentThread()); try{//delay a random amount of time Thread.currentThread().sleep( (int)(Math.random() * 100)); }catch(InterruptedException e){} }//end for-loop }//end run }//end class AnotherThreadClass |
.
New Thread
Runnable
Not Runnable
Dead
A Java thread is always in one of several states.
It's state indicates what it is currently doing and what it is capable of doing at that time. For example, it may be running, sleeping, dead, etc.
The Java Tutorial, by Campione and Walrath, as well as numerous other good books, provide diagrams which help you visualize the various possible states of a thread.
I recommend that you review one or more of those books and form a mental picture of the diagrams that they provide. (As of the original date of this writing, 2/1/97, many good Java books are currently available on-line at JavaSoft, Sams Publishing, Que Publishing, and probably at other sites as well.)
Campione and Walrath indicate in their diagram on page 295 that a thread can be in any of the following states:
They point out that their diagram also illustrates which method calls cause a transition to another state, and they state:
"This diagram is not a complete finite state diagram, but rather an overview of the more interesting and common facets of a thread's life." |
.
According to their definition, the instantiation of a Thread object creates a new thread but does not start it running. This is the state that they refer to as a New Thread.
In our program from above, the instantiation statement was as follows:
Thread threadA = new Thread(new OneThreadClass(),"thrdA"); |
Paraphrasing Campione and Walrath, when a thread is in this state, you can only start the thread or stop it. Calling any method besides start() or stop() causes an IllegalThreadStateException.
Campione and Walrath tell us that invoking the start() method on an instantiated Thread object
This puts the thread in what they call the Runnable state. They are careful to call this state Runnable rather than Running because the thread might not actually be running when it is in this state (it may not be winning in the competition for computer resources).
Because many computers have a single CPU, making it impossible to run all Runnable threads at the same time, the Java runtime system implements a scheduling scheme that shares the processor between all Runnable threads. A Runnable thread may actually be running, or may be awaiting its turn to run.
According to Campione and Walrath, a thread becomes Not Runnable when one of the following four events occurs:
You have already seen several examples of putting a thread into this state by invoking its sleep() method. One of them follows:
Thread.currentThread().sleep(1000); |
In this particular case, the run() method put itself to sleep for one second and became Not Runnable during that period.
During the one-second period period of sleep, the thread will not run even if the processor is available. (A thread can be awakened by invoking the interrupt() method on the sleeping thread object. Otherwise, it will not awaken until it has slept for the prescribed period of time.)
At the end of the prescribed interval, the thread automatically becomes Runnable again. Whether or not it will actually start running depends on its priority and the availability of the CPU.
According to Campione and Walrath,
"For each of the entrances into the "Not Runnable" state ... there is a specific and distinct escape route that returns the thread to the "Runnable" state. An escape route only works for its corresponding entrance. For example, if a thread has been put to sleep, then the specified number of milliseconds must elapse before the thread becomes "Runnable" again. Calling resume() on a sleeping thread has no effect." |
Campione and Walrath provide the following list of entrances to Not Runnable and the corresponding escape route.
Just like its human counterpart, a thread can die in two ways:
Campione and Walrath tell us that a thread dies naturally when its run() method exits normally.
You can also kill a thread at any time by calling its stop() method.
The following interesting terminology comes from Campione and Walrath:
"The stop() method throws a ThreadDeath object at the thread to kill it. Thus when a thread is killed in this manner it dies asynchronously. The thread will die when it actually receives the ThreadDeath exception." |
You may recall from the lesson on exception handling that, according to Java in a Nutshell by David Flanagan, ThreadDeath is actually derived from the Error class rather than the Exception class. Flanagan tells us,
"An Error generally signals that a non-recoverable error has occurred. They should not be caught and usually cause the Java interpreter to display a message an exit. An exception (to this rule) is the ThreadDeath error which causes the thread in which it is thrown to stop running, but which does not print an error message or affect other threads." |
.
I had hoped by now (January 1998) that JDK 1.0.2 would no longer be in use. However, due to the fact that browsers continue to linger behind in their support of JDK 1.1, that is not the case.
When I was using JDK 1.0.2, I encountered strange behavior when compiling and executing threaded programs using JDK 1.02 in a DOS box under Windows 95. In particular, some threads appeared to refuse to die. At least they refused to return control to the operating system when they do die.
Fortunately, this problem seems to have been resolved in JDK 1.1.1.
The runtime system throws an IllegalThreadStateException when you call a method on a thread and that thread's state does not allow for that method call.
As is the case for all "checked" exceptions, when you call a thread method that can throw an exception, you must either specify or catch the exception. This is illustrated in the calls to the sleep() method in the above sample programs.
The Thread class includes a method called isAlive(). The isAlive() method returns true if the thread has been started and not stopped (runnable or not runnable according to the earlier description of states).
According to Campione and Walrath
Preemptive Scheduling
Selfish Threads
Time-Slicing
The yield Method
As mentioned earlier, the priority of a thread can be adjusted relative to the priority of other threads. The priority of a specific thread tells the Java thread scheduler when this thread should run in relation to other threads.
Because all computers have a limited number of CPUs, all threads do not always run concurrently. Most computers have only one CPU, so threads usually run one at a time in such a way as to produce an illusion of concurrency.
The Java runtime supports a scheduling algorithm known as fixed priority scheduling. This algorithm schedules threads based on their priority relative to other "Runnable" threads.
A Java thread inherits its priority from the thread that created it.
You can modify a thread's priority at any time after its creation using the setPriority() method.
The following integer constants are defined in the Thread class:
The priority of an individual thread can be set to any integer value between and including these two extremes. The higher the integer value, the higher the priority.
When two or more threads are ready to be executed and system resource becomes available to execute a thread, the runtime system chooses the Runnable thread with the highest priority for execution.
According to Campione and Walrath,
"Only when that thread stops, yields, or becomes Not Runnable for some reason will a lower priority thread start executing." |
Further according to Campione and Walrath,
"If two threads of the same priority are waiting for the CPU, the scheduler chooses one of them to run in a round-robin fashion. The chosen thread will run until one of the following conditions is true:
Then the second thread is given a chance to run, and so on, until the interpreter exits." |
.
If a thread with a higher priority than all other Runnable threads becomes Runnable, the runtime system will preempt the running thread and choose the new higher priority thread for execution.
On systems which do not provide time slicing among threads of equal priority, one thread can acquire and hog the CPU, essentially preventing other threads of equal priority from having an opportunity to execute.
On some systems, Windows 95 for example, a strategy known as time-slicing is implemented to prevent a selfish thread from preventing other threads of equal priority from running.
According to Campione and Walrath,
"A time-sliced system divides the CPU into time slots and iteratively gives each of the equal-and-highest priority threads a time slot in which to run. The time-sliced system iterates through the equal-and-highest priority threads, allowing each one a bit of time to run, until one or more of them finishes or until a higher priority thread preempts them. Notice that time-slicing makes no guarantees as to how often or in what order threads are scheduled to run." |
Also, according to Campione and Walrath,
"The Java runtime does not implement (and therefore does not guarantee) time-slicing. However, some systems on which you can run Java do support time-slicing. Your Java programs should not rely on time-slicing as it may produce different results on different systems." |
.
A thread can voluntarily yield the CPU by calling the yield() method. The yield() method gives other threads of the same priority a chance to run. If there are no equal priority threads in the "Runnable" state, then the yield is ignored.
As mentioned earlier, a Daemon thread is one which belongs to the system rather than to the process that spawned it. Daemon threads often provide a service for other threads in the system. Any Java thread can be a daemon thread.
To specify that a thread is a Daemon thread, call the setDaemon() method with the argument true.
To determine if a thread is a Daemon thread, use the accessor method isDaemon().
This is our first mention of thread groups and we won't spend a lot of time on the topic.
All threads belong to a thread group.
The ThreadGroup class which is a member of the java.lang package, defines and implements the capabilities of a group of related threads.
Thread groups make it possible for you to collect various threads into a single object and manipulate them as a group rather than individually. For example, you could suspend all the threads within a group with a single statement. A variety of methods are available to manipulate threads as a group.
When a new thread is constructed, it is put into a thread group.
You can
You cannot move a thread to a new group after the thread has been created.
If you don't specify its group in the constructor, the system places a new thread in the same group as the thread that created it.
When a Java application starts, the system creates a ThreadGroup named "main".
Unless specified otherwise, all new threads become members of the "main" thread group.
This is evidenced by the output from the very first sample program in this lesson which is repeated below for convenience. We haven't discussed the format of that output yet.
Thread[threadA,5,main] Thread[threadB,5,main] Thread[main,5,main] |
When you use
System.out.println(Thread.currentThread()) |
to display information about a thread, as was the case in the sample program, what you get is
In the case of our sample program, all three threads belonged to the main group as shown above.
The Thread class provides constructors that you can use to specify the group for a thread when you instantiate it.
You can determine the group to which a thread belongs by calling its getThreadGroup() method.
Once you have that information, methods are available which allow you to query the group for other information such as what other threads belong to the group.
A great deal of capability surrounds the ThreadGroup class which we will not discuss here. You would be well-advised to familiarize yourself with those capabilities.
Fairness, Starvation, and Deadlock
The Producer/Consumer Model
Monitors
The notify and wait Methods
Up to this point we have assumed that all the threads were running asynchronously and independently of the others. However, often this is not the case.
There will often be two or more threads which must share the same resource such as a variable or an array.
Typically this means that the behavior of the two threads must be synchronized so that the action of one thread won't damage the other.
According to Campione and Walrath,
"If you write a program in which several concurrent threads are competing for resources, you must take precautions to ensure fairness. A system is fair when each thread gets enough access to limited resource to make reasonable progress. |
Continuing with Campione and Walrath,
"A fair system prevents starvation and deadlock. Starvation occurs when one or more threads in your program is blocked from gaining access to a resource and thus cannot make progress. Deadlock is the ultimate form of starvation; it occurs when two or more threads are waiting on a condition that cannot be satisfied. Deadlock most often occurs when two (or more) threads are each waiting for the other(s) to do something." |
.
Just about every book on Java programming explains the use of synchronized methods using a program that implements the producer/consumer model.
In this model, one thread is a producer of data while another thread is a consumer of the same data. There is a common data storage area where the producer puts the data and the consumer gets it.
In order to prevent problems, it is necessary to prevent the consumer from trying to get data from the common storage area while the producer is putting data into the storage area.
It is also necessary to prevent the producer from trying to put data into the common storage area when the consumer is getting data from the storage area.
We will follow the lead of others and illustrate thread synchronization by presenting a sample program that implements the producer/consumer model.
Our sample program will use a standard FIFO queue as the common data storage area.
Although the source code for the queue is given, we will assume that it is generally understood by everyone, and won't spend time discussing it, other than to state that a queue is a data structure commonly used in data processing where the first data element put into the structure is the first element taken out.
Many programming textbooks describe the checkout line at a supermarket as a queue (assuming that no one cuts the line). The first person to get in line is the first person to get checked out, the next person in line checks out next, etc.
We will instantiate a producer thread which puts data into the queue, and a consumer thread that gets data out of the queue, one byte at a time in both cases.
We will cause each of the threads to sleep() for a random period of time between attempts to put or get data.
We will write a QueueManager class that manages the putting of data into the queue and the getting of data out of the queue in a way that implements the producer/consumer model.
You might think of the QueueManager as a traffic cop directing traffic at a busy intersection, trying to prevent two cars from meeting in the middle of the intersection.
The programming equivalent of two cars meeting in the middle of the intersection often results in a condition commonly referred to as a race condition. Most Java books contain example programs that illustrate race conditions. You would be well-advised to review one of them.
The QueueManager will use the wait() and notify() methods to prevent collisions.
When the queue is full, the producer will be required to wait() until notified by the consumer that space is available in the queue.
When the queue is empty, the consumer will be required to wait() until notified by the producer that new data is available in the queue.
Each time the producer puts a byte in the queue, it will notify() the consumer of the availability of new data, just in case the consumer is in a wait() state due to an empty queue.
Similarly, each time the consumer gets a byte from the queue, it will notify() the producer that space is now available in the queue just in case the producer is in the wait() state due to a full queue.
Invoking the notify() method has no effect if there are no threads in the wait() state.
The program is allowed to run for one second and then terminates.
The program does not display the actual data flow into and out of the queue. That would require several pages to reproduce.
Rather, the program displays a message whenever the producer finds the queue full, or the consumer finds the queue empty.
Because of the random delays between get and put attempts, the program produces a different output every time it is run. One such output is shown in the comments at the beginning of the program.
Note that the queue was made very small in order to better-illustrate the issues surrounding full and empty queues.
In a real programming situation, the queue would probably be made quite large in an attempt to prevent the occurrence of a full queue or an empty queue. Up to a point, with a problem of this sort, the probability of encountering a full or empty queue decreases with the overall size of the queue.
Our sample program follows. This program illustrates synchronization of threads which is an extremely important concept.
Except for the code for the Queue class, your instructor will explain the operation of the program. Again, it is assumed that all students in the class understand the operation of a standard FIFO queue.
/*File Synch01.java Copyright 1997, R.G.Baldwin This program illustrates the Producer/Consumer model using wait() and notify() Tested using JDK 1.1.3 under Win95. The output for one particular run of the program was: Queue empty, waiting Queue empty, waiting Queue empty, waiting Queue empty, waiting Queue empty, waiting Queue empty, waiting Queue full, waiting Queue full, waiting Queue full, waiting Queue empty, waiting Queue empty, waiting Queue empty, waiting Queue empty, waiting Queue empty, waiting Queue empty, waiting Queue empty, waiting Terminating Consumer run method Terminating Producer run method **********************************************************/ class Synch01{ //Instantiate a class object named QueueManager which // will manage the producer/consumer model. static QueueManager queueManager = new QueueManager(); //used to tell the threads to terminate static boolean running = true; public static void main(String[] args){ //instantiate and start two threads Thread producer = new Producer(); Thread consumer = new Consumer(); producer.start(); consumer.start(); try{ //delay two seconds Thread.currentThread().sleep(2000); }catch(InterruptedException e){}; running = false;//signal the threads to terminate }//end main }//end class Synch01 //=======================================================// class Producer extends Thread { //producer thread public void run() { //run method for Producer thread byte byteToStore; //used to store data to be enqueued //Loop until running goes false while (Synch01.running){ //get a data byte byteToStore = (byte)(Math.random()*128); //Invoke the synchronized method to put the byte // in the queue Synch01.queueManager.putByteInQueue(byteToStore); //delay a random period of time try{ Thread.currentThread().sleep( (int)(Math.random()*100)); }catch(InterruptedException e){}; }//end while statement System.out.println("Terminating Producer run method"); }//end run method }//end class producer //=======================================================// class Consumer extends Thread { //consumer thread public void run() { //run method for Consumer thread //used to store the data read from the queue byte dataFromQueue; //Loop until running goes false while (Synch01.running) { //Invoke the synchronized method to get a byte // from the queue dataFromQueue = Synch01.queueManager.getByteFromQueue(); //delay a random amount of time try{ Thread.currentThread().sleep( (int)(Math.random()*100)); }catch(InterruptedException e){}; }//end while statement System.out.println("Terminating Consumer run method"); }//end run method }//end class consumer //=======================================================// //This class implements the Producer/Consumer model by // managing a queue as a shared resource. class QueueManager{ Queue queue; //-------------------------------------------------------// QueueManager(){//constructor queue = new Queue();//instantiate a queue object }//end constructor //-------------------------------------------------------// synchronized void putByteInQueue(byte incomingByte){ //This synchronized method places a byte in the queue // If the queue is full, wait(). If still full when // wait() terminates, wait again. Called by the // producer thread to put a byte in the queue. try{ while(queue.isFull()){ System.out.println("Queue full, waiting"); wait(); }//end while loop }catch (InterruptedException E){ System.out.println("InterruptedException: " + E); }//end catch block //put the byte into the queue queue.enQueue(incomingByte); //wake up getByteFromQueue() if it has invoked wait(). notify(); }//end method putByteInQueue() //-----------------------------------------------------// public synchronized byte getByteFromQueue(){ //This synchronized method removes a byte from the // queue. If the queue is empty, wait(). If still // empty when wait() terminates, wait again. Called by // consumer thread to get a byte from the queue try{ while(queue.isEmpty()){ System.out.println("Queue empty, waiting"); wait(); }// end while }catch (InterruptedException E){ System.out.println("InterruptedException: " + E); }//end catch block //get the byte from the queue byte data = queue.deQueue(); //wake up putByteInQueue() if it has invoked wait(). notify(); return data; }//end getByteFromQueue() }//end class QueueManager //=======================================================// //This is a standard FIFO queue class. class Queue{ //constant defining maximum queue size static final int MAXQUEUE = 4; byte[] queue = new byte[MAXQUEUE]; int front, rear; Queue(){//constructor front = rear = 0; }//end constructor void enQueue(byte item){ queue[rear] = item; rear = next(rear); }//end method enQueue byte deQueue(){ byte temp = queue[front]; front = next(front); return temp; }//end method deQueue boolean isEmpty(){ return front == rear; }//end isEmpty boolean isFull(){ return (next(rear) == front); }//end isFull int next(int index){ return (index+1 < MAXQUEUE ? index+1 : 0); }//end next }//end Queue class //=======================================================// |
.
We will also be discussing parts of this program throughout the remainder of this lesson.
There is a long-standing computer science concept known as a monitor as described below.
Objects such as the queue in the above example that are shared between threads and whose access must be synchronized are known as condition variables.
According to Campione and Walrath,
"The Java language and runtime system support thread synchronization through the use of monitors, which were first outlined in C. A. R. Hoare's article Communicating Sequential Processes (Communications of the ACM, Vol. 21, No. 8, August 1978, pp. 666-677). In general, a monitor is associated with a specific data item (a condition variable) and functions as a lock on that data. When a thread holds the monitor for some data item, other threads are locked out and cannot inspect or modify the data." |
If two or more threads have code that access the same data, that code is known as a critical section. In Java, you use the synchronized keyword to mark critical sections of code.
You will usually mark entire methods as critical sections using the synchronized keyword. It is also possible to mark smaller code segments as synchronized.
However, according to Campione and Walrath,
"this violates object-oriented paradigms and leads to confusing code that is difficult to debug and maintain. For the majority of your Java programming purposes, it's best to use synchronized only at the method level." |
A unique monitor is associated with every object that has a synchronized method.
Had we instantiated more than one object of the QueueManager class, every object of that class would have had its own unique monitor.
There is a very important point that is explained in the book, Java Primer Plus by Tyma, Torok, and Downing,
"Each object has only one lock. Therefore, if an object has three synchronized methods, a thread entering any of the methods will set the lock. After that, no thread can enter any of the synchronized methods until the first exits the method it was executing and frees the lock." |
They caution that because of this, you will want to be very careful as to how you group synchronized methods in classes. You should not put two or more synchronized methods in the same class unless it is acceptable for all of them to be locked whenever a thread enters any one of them.
They also point out that static methods can be synchronized, and static methods use the lock belonging to the class rather than a lock belonging to any particular object.
To summarize, when control enters a synchronized method, the thread that called the method acquires the lock on the object. Other threads cannot invoke any synchronized method on the same object until the lock is released.
In our sample program, when the producer invokes the putByteInQueue() method to put data into the queue, this locks the queue and prevents the consumer from being able to invoke the getByteFromQueue() method to get data from the queue.
In our case, this is what we want to happen so having both methods in the same object with a common lock meets our requirements.
If a thread has a lock on an object and invokes the wait() method, the lock is temporarily released thereby allowing another thread to gain access to the object.
In our example, the producer invokes wait() on a full queue, temporarily releasing the lock and allowing access by the consumer (who will get a byte to make it not full).
Also in our example, the consumer invokes wait() on an empty queue, temporarily releasing the lock to allow access by the producer (who will put a byte into the queue to make it not empty).
During normal (non-full) operation, when the putByteInQueue() method returns, the producer releases the lock on the queue.
When the consumer calls the getByteFromQueue() method, the consumer acquires a lock on the queue preventing the producer from calling the putByteInQueue() method.
According to Campione and Walrath,
"The Java runtime system allows a thread to re-acquire a monitor that it already holds because Java monitors are re-entrant. Re-entrant monitors are important because they eliminate the possibility of a single thread deadlocking itself on a monitor that it already holds." |
They also provide an example program which illustrates the value of this feature if you are interested in pursuing it further.
The notify() method
The wait() Method
Other Versions of the wait() method
The sample program uses the notify() and wait() methods of the Object class to coordinate the activities of the producer and the consumer.
The QueueManager uses notify() and wait() to prevent collisions in the attempts of the producer to put data into the queue, and the attempts of the consumer to get data from the queue.
Only the producer or the consumer but not both is allowed to access the queue at any given time. That is the essence of a monitor. Only one thread can be in a monitor at any given time.
The notify() and wait() methods are members of the java.lang.Object class.
These methods can be invoked only from within a synchronized method or within a synchronized block or statement.
The getByteFromQueue() method calls notify() at the end of the method.
The notify() method chooses a waiting thread and wakes it up.
In the case of our sample program, the consumer holds the lock on the queue during the execution of the getByteFromQueue() method. Immediately before termination, the getByteFromQueue() method call notify() to wake up the producer if it is waiting. If it is not waiting, the call to notify() is simply ignored.
As an aside, according to Campione and Walrath,
"If multiple threads are waiting for a monitor, the Java runtime system chooses one of the waiting threads, making no commitments or guarantees about which thread will be chosen." |
Similarly, the producer holds the lock on the queue during the execution of the putByteInQueue() method which calls notify() before it terminates to wake up the consumer if it is waiting.
According to Campione and Walrath,
"The Object class has another method -- notifyAll() -- that wakes up all the threads waiting on the same monitor. In this situation, the awakened threads compete for the monitor. One thread gets the monitor and the others go back to waiting. " |
.
You can use wait() and notify() to coordinate the activities of multiple threads using the same resources.
The wait() method causes the current thread to wait until another thread notifies it of a condition change.
In our program, if the getByteFromQueue() method finds the queue empty, it
Releasing the lock allows the putByteInQueue() method to put a new byte in the queue making it no longer empty. putByteInQueue() then invokes notify() to awaken getByteFromQueue().
Similarly, if the putByteInQueue method finds the queue full, it
Releasing the lock allows the getByteFromQueue() method to get a byte from the queue, making it no longer full. getByteFromQueue() then invokes notify() to awaken putByteInQueue().
When a thread enters the wait() method the monitor is released, and when the thread exits the wait() method, the monitor is acquired again.
In our example, this gives the other thread an opportunity to acquire the lock on the queue and correct the problem which caused the first thread to enter the wait() method (full queue or empty queue) in the first place.
The Object class contains two other versions of the wait() method which wake up automatically (do not wait indefinitely for notification):
Despite all of this, it is possible to have programs that become deadlocked when each thread is waiting on a resource that cannot become available.
The simplest form of deadlock is when two threads are each waiting on a resource that is locked by the other thread.
Campione and Walrath provide a good explanation of a classic deadlock situation commonly called the Dining Philosophers along with some suggestions as to how to prevent deadlock. You are encouraged to study that material. The on-line version includes an applet that allows you to experiment with certain parameters that impact the potential for deadlock.
Q - What is the definition of multi-threaded programming according to Patrick Naughton?
A - According to The Java Handbook, by Patrick Naughton,
"Multi-threaded programming is a conceptual paradigm for programming where you divide programs into two or more processes which can be run in parallel."
Q - Multithreading refers to two or more programs executing, "apparently" concurrently, under control of the operating system. The programs need have no relationship with each other, other than the fact that you want to start and run them all concurrently. True or False? If false, explain why.
A - False. That is a description of multiprocessing, not multithreading. Multithreading refers to two or more tasks executing, "apparently" concurrently, within a single program.
Q - According to current terminology, the term blocked means that the thread is waiting for something to happen and is not consuming computer resources. True or False? If false, explain why.
A - True.
Q - What are the two ways to create threaded programs in Java?
A - In Java, there are two ways to create threaded programs:
Q - What two steps are required to spawn a thread in Java?
A - The two steps necessary to spawn a thread in Java are:
Q - How do you start a thread actually running in Java?
A - Invoke the start() method on object of the Thread class or of a subclass of the Thread class.
Q - It is always possible to extend the Thread class in your Java applications and applets. True or False? If false, explain why.
A - False. Sometimes it is not possible to extend the Thread class, because you must extend some other class and Java does not support multiple inheritance.
Q - Although multithreaded programming in Java is possible, it is also possible to write Java programs that do not involve threads: True or False? If false, explain why.
A - False. The main method itself runs in a thread which is started by the interpreter.
Q - What is the name of the method that can be used to determine if a thread is alive?
A - The name of the method is isAlive().
Q - Once you start two or more threads running, unless you specify otherwise, they run synchronously and independently of one another: True or False? If false, explain why.
A - False. Once you start two or more threads running, unless you specify otherwise, they run asynchronously and independently of one another.
Q - The process of keeping one thread from corrupting the data while it is being processed by another thread is known as synchronization: True or False? If false, explain why.
A - True.
Q - Java allows you to specify the absolute priority of each thread: True or False? If false, explain why.
A - False. Java allows you to specify the priority of each thread relative to other threads but not on an absolute basis.
Q - Thread synchronization can be achieved using wait(), notify(), and notifyAll() which are methods of the Thread class: True or False? If false, explain why.
A - False. wait(), notify(), and notifyAll() are not methods of the Thread class, but rather are methods of the Object class.
Q - When you implement a threaded program, you will always override the _____________ method of the Thread class and build the functionality of your threaded program into that method. What is the name of the method?
A - The run() method.
Q - In a multithreaded program, you will start a thread running by invoking the __________ method on your Thread object which will in turn invoke the ___________ method. What are the names of the missing methods, and what are the required parameters for each method?
A - In a multithreaded program, you will start a thread running by invoking the start() method on your Thread object which will in turn invoke the run() method. Neither method takes any parameters.
Q - What do Campione and Walrath list as the four possible states of a thread?
A - Campione and Walrath list the following possible states for a thread:
Q - What methods can be invoked on a thread object which is in the state that Campione and Walrath refer to as a New Thread and what will happen if you invoke any other method on the thread?
A - When a thread is in this state, you can only start the thread or stop it. Calling any method other than start() or stop() will cause an IllegalThreadStateException.
Q - What, according to Campione and Walrath, will cause a thread to become Not Runnable?
A - According to Campione and Walrath, a thread becomes Not Runnable when one of the following four events occurs:
Q - Write a program that meets the following specifications.
/*File SampProg104.java from lesson 58 Copyright 1997, R.G.Baldwin Without viewing the solution that follows, write a Java application that illustrates the instantiation and running of two threads by implementing the runnable interface (as opposed to extending the Thread class). Use a version of constructor that allows you to specify a name for the new thread. The output displays information about the running threads and should be similar to the following: Thread[threadA,5,main] Thread[threadB,5,main] Thread[main,5,main] =========================================================== */ class SampProg104{ static public void main(String[] args){ //Instantiate two new thread objects Thread threadA = new Thread(new MyThread(),"threadA"); Thread threadB = new Thread(new MyThread(),"threadB"); //Start them running threadA.start(); threadB.start(); try{ Thread.currentThread().sleep(1000);//delay one second }catch(InterruptedException e){} //Display info about the main thread System.out.println(Thread.currentThread()); //stop the two threads which were started above threadA.stop(); threadB.stop(); }//end main }//end class SampProg104 class MyThread implements Runnable{ public void run(){//override run method //Display info about this particular thread System.out.println(Thread.currentThread()); }//end run }//end class MyThread |
Q - Write a program that meets the following specifications.
/*File SampProg105.java from lesson 58 Copyright 1997, R.G.Baldwin Without viewing the solution that follows, write a Java application that illustrates the instantiation and running of two threads by extending the Thread class (as opposed to implementing the Runnable class). Use a version of constructor that allows you to specify a name for the new thread. The output should display information about the running threads and should be similar to the following: Thread[threadA,5,main] Thread[threadB,5,main] Thread[main,5,main] =========================================================== */ class SampProg105{ static public void main(String[] args){ //Instantiate two new thread objects Thread threadA = new Thread(new MyThread(),"threadA"); Thread threadB = new Thread(new MyThread(),"threadB"); //Start them running threadA.start(); threadB.start(); try{ Thread.currentThread().sleep(1000);//delay one second }catch(InterruptedException e){} //Display info about the main thread System.out.println(Thread.currentThread()); //stop the two threads which were started above threadA.stop(); threadB.stop(); }//end main }//end class SampProg105 class MyThread extends Thread{ public void run(){//override run method //Display info about this particular thread System.out.println(Thread.currentThread()); }//end run }//end class MyThread |
Q - Write a program that meets the following specifications.
/*File SampProg106.java from lesson 58 Copyright 1997, R.G.Baldwin Without viewing the solution that follows, write a Java application that illustrates the instantiation and running of two threads by extending the Thread class. Use a version of the thread constructor that doesn't take any arguments. The output should be similar to the following: Thread[Thread-1,5,main] Thread[Thread-2,5,main] Thread[main,5,main] =========================================================== */ class SampProg106{ static public void main(String[] args){ //Instantiate two new thread objects Thread threadA = new MyThread(); Thread threadB = new MyThread(); //Start them running threadA.start(); threadB.start(); try{ Thread.currentThread().sleep(1000);//delay one second }catch(InterruptedException e){} //Display info about the main thread System.out.println(Thread.currentThread()); //stop the two threads which were started above threadA.stop(); threadB.stop(); }//end main }//end class SampProg106 class MyThread extends Thread{ public void run(){ //Display info about this particular thread System.out.println(Thread.currentThread()); }//end run }//end class MyThread |
Q - Write a program that meets the following specifications.
/*File SampProg107.java from lesson 58 Copyright 1997, R.G.Baldwin Without viewing the solution that follows, write a Java application that illustrates the instantiation and running of two threads using the runnable interface. Cause each of two threads to count from zero to four, displaying the count, and sleeping a random amount of time between counts. The output should be similar to the following: Thread[thrdA,5,main] cnt is 0 Thread[threadB,5,main] cnt is 0 Thread[threadB,5,main] cnt is 1 Thread[threadB,5,main] cnt is 2 Thread[thrdA,5,main] cnt is 1 Thread[thrdA,5,main] cnt is 2 Thread[threadB,5,main] cnt is 3 Thread[thrdA,5,main] cnt is 3 Thread[threadB,5,main] cnt is 4 Thread[thrdA,5,main] cnt is 4 =========================================================== */ class SampProg107{ static public void main(String[] args){ //Instantiate two new thread objects with names of // different lengths which helps when viewing the // output. Thread threadA = new Thread(new MyThread(),"thrdA"); Thread threadB = new Thread(new MyThread(),"threadB"); //Start them running threadA.start(); threadB.start(); try{ Thread.currentThread().sleep(1000);//delay one second }catch(InterruptedException e){} //stop the two threads which were started above threadA.stop(); threadB.stop(); }//end main }//end class SampProg107 class MyThread implements Runnable{ public void run(){//override the run method for(int cnt = 0; cnt < 5; cnt++){ System.out.println(Thread.currentThread() + " cnt is " + cnt); try{//delay a random amount of time Thread.currentThread().sleep( (int)(Math.random() * 100)); }catch(InterruptedException e){} }//end for-loop }//end run }//end class MyThread //end program |
Q - Write a program that meets the following specifications.
/*File SampProg108.java from lesson 58 Copyright 1997, R.G.Baldwin Without viewing the solution that follows, write a Java application that illustrates the instantiation and running of four different threads using the runnable interface where two different thread classes are defined each of which uses the Runnable interface. The run() method in one class is different from the run() method in the other class. Two Thread objects are instantiated and started for each of the two thread classes, resulting in four different threads running. Each of the threads counts from zero to four and displays the count, sleeping a random amount of time between counts. Make the format of the display different between the two thread classes. The output should be similar to the following: Thread[thrdA,5,main] cnt is 0 Thread[threadB,5,main] cnt is 0 The actual cnt is 0 Thread[Xthrd,5,main] The actual cnt is 0 Thread[Ythread,5,main] Thread[thrdA,5,main] cnt is 1 Thread[threadB,5,main] cnt is 1 Thread[thrdA,5,main] cnt is 2 The actual cnt is 1 Thread[Ythread,5,main] The actual cnt is 1 Thread[Xthrd,5,main] Thread[threadB,5,main] cnt is 2 The actual cnt is 2 Thread[Ythread,5,main] Thread[thrdA,5,main] cnt is 3 The actual cnt is 3 Thread[Ythread,5,main] Thread[thrdA,5,main] cnt is 4 The actual cnt is 2 Thread[Xthrd,5,main] The actual cnt is 4 Thread[Ythread,5,main] Thread[threadB,5,main] cnt is 3 The actual cnt is 3 Thread[Xthrd,5,main] The actual cnt is 4 Thread[Xthrd,5,main] Thread[threadB,5,main] cnt is 4 =========================================================== */ class SampProg108{ static public void main(String[] args){ //Instantiate two new thread objects of one type Thread threadA = new Thread( new OneThreadClass(),"thrdA"); Thread threadB = new Thread( new OneThreadClass(),"threadB"); //Instantiate two new thread objects on another type Thread Xthread = new Thread( new AnotherThreadClass(),"Xthrd"); Thread Ythread = new Thread( new AnotherThreadClass(),"Ythread"); //Start them running threadA.start(); threadB.start(); Xthread.start(); Ythread.start(); try{ Thread.currentThread().sleep(1000);//delay one second }catch(InterruptedException e){} //stop the four threads which were started above threadA.stop(); threadB.stop(); Xthread.stop(); Ythread.stop(); }//end main }//end class SampProg108 class OneThreadClass implements Runnable{ public void run(){ for(int cnt = 0; cnt < 5; cnt++){ System.out.println(Thread.currentThread() + " cnt is " + cnt); try{//delay a random amount of time Thread.currentThread().sleep( (int)(Math.random() * 100)); }catch(InterruptedException e){} }//end for-loop }//end run }//end class OneThreadClass class AnotherThreadClass implements Runnable{ public void run(){ for(int cnt = 0; cnt < 5; cnt++){ System.out.println("The actual cnt is " + cnt + " " + Thread.currentThread()); try{//delay a random amount of time Thread.currentThread().sleep( (int)(Math.random() * 100)); }catch(InterruptedException e){} }//end for-loop }//end run }//end class AnotherThreadClass //end program |
Q - Write a program that meets the following specifications.
/*File SampProg109.java from lesson 58 Copyright 1997, R.G.Baldwin Without viewing the solution that follows, write a Java application that illustrates thread synchronization by implementing the Producer/Consumer model using wait() and notify(). Then view the following solution and compare it with your solution to confirm that you have properly synchronized your threads using the Producer/Consumer model. **********************************************************/ class SampProg109{ //instantiate a class object named QueueManager which // will manage the producer/consumer model. static QueueManager queueManager = new QueueManager(); //Following variable is used later to to tell the // threads to terminate static boolean running = true; public static void main(String[] args){ //instantiate and start two threads Thread producer = new Producer(); Thread consumer = new Consumer(); producer.start(); consumer.start(); try{ Thread.currentThread().sleep(2000);//delay two sec }catch(InterruptedException e){}; running = false;//signal the threads to terminate }//end main }//end class SampProg109 //======================================================== class Producer extends Thread { //producer thread public void run() { //override run method byte byteToStore; //used to store data to be enqueued //loop until running goes false while (SampProg109.running) { byteToStore = (byte)(Math.random()*128);//get data //Invoke the synchronized method to put the byte // in the queue SampProg109.queueManager.putByteInQueue( byteToStore); //delay a random period of time try{ Thread.currentThread().sleep( (int)(Math.random()*100)); }catch(InterruptedException e){}; }//end while statement System.out.println( "Terminating Producer run method"); }//end run method }//end class producer //========================================================= class Consumer extends Thread { //consumer thread public void run() { //override run method //Variable used to store the data read from the queue byte dataFromQueue; //loop until running goes false while (SampProg109.running) { //Invoke the synchronized method to get a byte from // the queue dataFromQueue = SampProg109.queueManager.getByteFromQueue(); //delay a random amount of time try{ Thread.currentThread().sleep( (int)(Math.random()*100)); }catch(InterruptedException e){}; }//end while statement System.out.println("Terminating Consumer run method"); }//end run method }//end class consumer //========================================================= //This class implements the Producer/Consumer model by // managing a queue as a shared resource. class QueueManager{ Queue queue; QueueManager(){//constructor queue = new Queue();//instantiate a queue object }//end constructor synchronized void putByteInQueue(byte incomingByte){ //This synchronized method places a byte in the queue // If the queue is full, wait(). If still full when // wait() terminates, wait again. Called by the // producer thread to put a byte in the queue. try{ while(queue.isFull()){ System.out.println("Queue full, waiting"); wait(); }//end while loop }catch (InterruptedException E){ System.out.println("InterruptedException: " + E); }//end catch //put the byte into the queue queue.enQueue(incomingByte); //Wake up getByteFromQueue() if it has invoked wait(). notify(); }//end method putByteInQueue() //-------------------------------- public synchronized byte getByteFromQueue(){ //This synchronized method removes a byte from the // queue. If the queue is empty, wait(). If still // empty when wait() terminates, wait again. Called by // consumer thread to get a byte from the queue try{ while(queue.isEmpty()){ System.out.println("Queue empty, waiting"); wait(); }// end while }catch (InterruptedException E){ System.out.println("InterruptedException: " + E); }//end catch //get the byte from the queue byte data = queue.deQueue(); //Wake up putByteInQueue() if it has invoked wait(). notify(); return data; }//end getByteFromQueue() }//end class QueueManager //========================================================= //This is a standard FIFO queue class. class Queue{ //constant defining maximum queue size static final int MAXQUEUE = 4; byte[] queue = new byte[MAXQUEUE]; int front, rear; Queue(){//constructor front = rear = 0; }//end constructor void enQueue(byte item){ queue[rear] = item; rear = next(rear); }//end method enQueue byte deQueue(){ byte temp = queue[front]; front = next(front); return temp; }//end method deQueue boolean isEmpty(){ return front == rear; }//end isEmpty boolean isFull(){ return (next(rear) == front); }//end isFull int next(int index){ return (index+1 < MAXQUEUE ? index+1 : 0); }//end next }//end Queue class //========================================================= |
-end-