Suspending and Resuming Threads
Suspending a thread is when we temporarily pause a thread, rather than stop and destroy it. This thread can be resumed at a later stage.
When Java was first introduced threads were a part of the language and worked fine. There were some difficulties with multi-Platform versions. With the introduction of Java 2 threads were updated to prevent certain race and lock conditions that occurred. In the
Thread class there were
resume() methods, but these have been deprecated for JDK1.2+ and so should not be used - you will get deprecated warnings from the Java compiler if used, and they will lead to unpredictable threaded code if these warnings are ignored.
Figure 8.3, “A Multi-Threaded Counter” displays a capture of an application running in which there are two TextFields object that are controlled by the same buttons, but they are different threads that start at different points
50 and count at different rates. The first one counts every 100ms, whereas the second counts every 200ms (i.e. at half the speed).
Figure 8.3 A Multi-Threaded Counter (a) shows the count values at the start, (b) shows the count values after a few seconds, where the first counter has passed the second counter.
The source code is as below:
I have changed the code from the previous section to have two separate classes - the application and the . I did this to prevent any confusion between the Frame class and the thread itself, and to allow for the creation of two separate thread objects. The Frame class creates three buttons as in Figure 8.3, “A Multi-Threaded Counter Example” and two
TextField objects. So how does this code work?
CounterApplication1 creates two threads
count1 that passes a reference to the
count1Field, sets the counter to 0 and the delay between counting to 100ms and
count2 that passes a reference to the
count2Field and sets the counter to 50 and the delay between counting to 200ms.
actionPerformed() method handles the start button, that calls the
start() method of both
Counter objects. The "Stop" button and the "Toggle" button call the
toggleCount() methods of both
Counter class extends the
Thread class and over-rides the
run() inherited from
Counter class has a single constructor that requires a
TextField reference, an int delay and an int starting count value.
There are two boolean states
running is a state that defines if the
run() method is looping. The
paused state defines if the
wait() is to be called during the loop.
To stop the counter the
stopCount() can be called from outside the class. The
stopCounter simply sets the
running state to false, so that the next time the loop runs to completion, the
while(running) no longer is
true and so the loop ends. Once the thread has been stopped it has run to completion and cannot be re-started as the object has been destroyed.
toggleCounter() is a good bit more complex. The idea is straightforward - when the
toggleCounter() is called the
paused state changes from
false and then back again the next time it is called. When the
paused state is
true then the
wait() method is called and so the loop pauses at this very point. In addition, if the
paused state is
true when the "toggle" button is pressed, then we must un-pause the counter. To do this we must call the Thread's
notify() method to let it know that there has been a change in the states of the class. We must also use the
synchronized modifier on the
notify() methods. This is required by the language, and ensures that
notify are properly synchronized, eliminating the race conditions that could cause the "suspended" thread to miss a notify and remain suspended indefinitely. Rather than place the
synchronized on the entire
run() method, we have localized it to the
wait() method. We will discuss synchronized shortly.
wait() method (from the
Object class) causes a thread to release the lock it is holding on an object - allowing another thread to run. It can only be invoked from within a block of synchronized code and should be wrapped in a try block as it throws an
IOException. There are tree different
wait() wait for ever!
wait(long timeout) with a timeout
wait(long timeout, int nanos) with the time measured in nanoseconds
wait() can only be invoked by the thread that currently owns the lock on the object. Once the
wait() is called the thread becomes disabled for scheduling and is dormant until one of the following things happens:
Some other thread invokes the
notify() method for this object and the scheduler runs the thread.
Some other thread invokes the
notifyAll() method for this object and the scheduler runs the thread.
Some other thread interrupts this thread.
If a time was provided, and it has elapsed.
Once one of these things happens the thread then becomes available to the scheduler.
notifyAll() methods are defined in the
Object class. Like the
wait() method, they can only be used within synchronized code. The
notify() method wakes up a single thread which is waiting on the object's lock. If there was more than one thread waiting then on the object's lock then the choice of which waiting thread should be chosen is arbitrary -
notifyAll() awakens all threads waiting on the object's lock and the scheduler will decide which one to run. If you call
notify() on an object with no waiting threads, then the call will just be ignored.
Try this yourself! - An Up/Down Counter
Task: Modify the code as shown in Figure 8.3, “A Multi-Threaded Counter Example” to add the following functionality:
You can see a screen-grab of my solution in Figure 8.4, “My Up/Down Counter Example”.
Figure 8.4 The Up/Down Counter Example, for you to try yourself.
Solution: The solution is here -
UpDownCounterApp.java but please do not look at it until you have had a good attempt yourself.
Java has thread scheduling that monitors all running threads in all programs and decides which thread should be running. There are two main type of thread:
Priority Threads - Are regular, user-defined threads.
Daemon Threads - Are low-priority threads (often called service threads) that provide services to programs, when the load on the CPU is low. The Garbage Collector Thread is an example of a daemon thread. It is possible for use to convert a user thread into a daemon thread, or vice-versa, using the
Thread method. If there are only daemon threads running, the scheduler will exit.
There are two main forms of scheduler: (i) Preemptive that gives a certain time slice to each thread. The scheduler sets up the order that the threads run in. (ii) Non-preemptive that runs a thread until it is complete. Each thread has control of the processor for as long as it requires.
New threads inherit the priority and daemon flag from the thread that created it.
The scheduler decides which thread should be running based on a priority value assigned to the thread. The priority number has a value between 1 and 10 and a thread runs more often if it has a higher priority value. There are three pre-defined priorities:
Thread.MIN_PRIORITY - has the priority value of 1.
Thread.NORM_PRIORITY - Normally a thread has a priority value of 5.
Thread.MAX_PRIORITY - has the priority value of 10.
You can use the
setPriority() to set the priority level of a thread, so if you wished to set a
Thread object called
testThread to be running at the highest priority possible, you could call
testThread.setPriority(Thread.MAX_PRIORITY); The method
getPriority can be used to return the priority level of a given thread. It returns an int value, as per the list above.
An Example of Priorities and Threads
I have written a short example based on the code in the previous section to show you the effect of priorities on threads. This example sets the first
Counter object to have the maximum priority and the second
Counter object to have the minimum priority. The first counter starts with a value of 0 and the second counter starts with the higher value of 200. I set the delay between counting to be 1ms, in the hope that my machine will not be able to handle that speed of counting and so will give more priority to the first counter. You can see the results in Figure 8.5, where a screen-grab of this example running shows that the first counter has caught up with the second counter after ~20300ms, i.e. around 20 seconds.
Figure 8.5 The Counter Example, with different thread priorities (a) you can see that count 1 has a lower value but at a later time in (b) you can see that counter 1 has a higher value than counter 2.
The code for this example is below, with the changes highlighted in yellow (please note, I am using the same Counter class, I have had to rename it in order that I can distributed the notes in a single ee402 package):