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 suspend()
and 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 11.3, “A Suspend/Resume Multi-Counter Example” displays a capture of an applet that has suspend/resume functionality on
two separate threads.
CounterApplet.html
show this applet
running. Note that the two textfields are controlled by the same buttons, but they are different threads that
start at different points 0
and 50
and count at different rates. The first
one counts every 100ms, whereas the second counts every 200ms (i.e. at half the speed).
The source code is as below and as in
CounterApplet.java
.
1 2 3 // A Working Counting Applet - by Derek Molloy 4 5 import java.applet.*; 6 import java.awt.*; 7 import java.awt.event.*; 8 9 public class CounterApplet extends Applet implements ActionListener 10 { 11 private Button start, stop, toggle; 12 private TextField countField1, countField2; 13 private Counter theCounter1, theCounter2; 14 15 public void init() 16 { 17 countField1 = new TextField(10); 18 countField2 = new TextField(10); 19 this.add(countField1); 20 this.add(countField2); 21 22 start = new Button("Start"); 23 start.addActionListener(this); 24 this.add(start); 25 26 stop = new Button("Stop"); 27 stop.addActionListener(this); 28 this.add(stop); 29 30 toggle = new Button("Toggle"); 31 toggle.addActionListener(this); 32 this.add(toggle); 33 34 this.theCounter1 = new Counter(countField1, 100, 0); 35 this.theCounter2 = new Counter(countField2, 200, 50); 36 } 37 38 public void actionPerformed(ActionEvent e) 39 { 40 if (e.getSource().equals(start)) 41 { 42 this.theCounter1.start(); 43 this.theCounter2.start(); 44 } 45 else if (e.getSource().equals(stop)) 46 { 47 this.theCounter1.stopCounter(); 48 this.theCounter2.stopCounter(); 49 } 50 else if (e.getSource().equals(toggle)) 51 { 52 this.theCounter1.toggleCounter(); 53 this.theCounter2.toggleCounter(); 54 } 55 } 56 } 57 58 59 60 class Counter extends Thread 61 { 62 private int count, delay; 63 private boolean running = true, paused = false; 64 private TextField theField; 65 66 67 public Counter(TextField t, int delay, int startValue) 68 { 69 this.theField = t; 70 this.count = startValue; 71 this.delay = delay; 72 } 73 74 public void run() 75 { 76 while (running) 77 { 78 theField.setText("Count: "+this.count++); 79 try 80 { 81 Thread.currentThread().sleep(this.delay); 82 83 synchronized(this) { 84 while (this.paused) 85 wait(); 86 } 87 } 88 catch (InterruptedException e) 89 { 90 theField.setText("Interrupted!"); 91 this.stopCounter(); 92 } 93 } 94 } 95 96 public int getCount() { return count; } // not used in this example 97 98 public void stopCounter() { this.running = false; } 99 100 public synchronized void toggleCounter() 101 { 102 this.paused = !this.paused; 103 if (!this.paused) notify(); 104 } 105 } 106 107
I have changed the code from the section called “A Working Counter” to have two separate classes. I did this to
prevent any confusion between the Applet
class and the thread itself, and to allow for
the creation of two separate thread objects. The Applet
class creates three buttons as
in Figure 11.3, “A Suspend/Resume Multi-Counter Example” and two TextField
objects. So how does this code
work?
The CounterApplet
creates two threads theCounter1
that
passes a reference to the countField1
, sets the counter to 0 and the delay between
counting to 100ms and theCounter2
that passes a reference to the countField2
and sets the counter to 50 and the delay between counting to 200ms.
The actionPerformed()
method handles the start button, that calls the
start()
method of both Counter
objects. The "Stop" button
and the "Toggle" button call the stopCounter()
and toggleCounter()
methods of both Counter
objects.
The Counter
class extends the Thread
class and
over-rides the run()
inherited from Thread
.
The 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
and paused
.
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 stopCounter()
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.
The toggleCounter()
is a good bit more complex. The idea is
straightforward - when the toggleCounter()
is called the paused
state changes from true
to false
and then back again the next time
it is called. When the paused
state is true
then the
Thread
's wait()
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
wait()
and notify()
methods. This is required
by the language, and ensures that wait()
and notify
are properly serialized. It eliminates 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 localised it to the wait()
method. We will discuss synchronized in the section called “Adding Synchronization to Code”
The 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()
methods:
wait()
wait for ever!
wait(long timeout)
with a timeout
wait(long timeout, int nanos)
with the time measured in nanoseconds
The 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, that it has elapsed.
Once one of these things happens the thread then becomes available to the scheduler.
The notify()
and 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.
Task: Modify the applet as shown in Figure 11.3, “A Suspend/Resume Multi-Counter Example” to add the following functionality:
Add a new button called Up/Down that when pressed causes the two threaded counters to count the opposite way.
Add a new feature to the Counter
constructor called maximum, that
allows you to choose a value to count to.
You can see a screen-grab of my solution in Figure 11.4, “My Up/Down Counter Solution” and you can see it running
here - UpDownCounterApplet.html
. For
my example I have set the maximum value of each counter to 200. Note that the slower counter will continue to
count until it too reaches 200.
Solution: The solution is here -
UpDownCounterApplet.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 setDaemon(boolean)
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.
I have written a short example based on the code in the section called “Suspending and Resuming Threads”
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 11.5, “Priority Counter Example”, where a screen-grab of this example running shows that the
first counter has caught up with the second counter after ~65526ms, i.e. just over 1 minute. You can also
run it here -
PriorityCounterApplet.html
.
The code for this example is below and in
PriorityCounterApplet.java
1 2 3 // A Working Counting Scheduled Applet - by Derek Molloy 4 5 import java.applet.*; 6 import java.awt.*; 7 import java.awt.event.*; 8 9 public class PriorityCounterApplet extends Applet implements ActionListener 10 { 11 private Button start, stop, toggle; 12 private TextField countField1, countField2; 13 private Counter theCounter1, theCounter2; 14 15 public void init() 16 { 17 countField1 = new TextField(10); 18 countField2 = new TextField(10); 19 this.add(countField1); 20 this.add(countField2); 21 22 start = new Button("Start"); 23 start.addActionListener(this); 24 this.add(start); 25 26 stop = new Button("Stop"); 27 stop.addActionListener(this); 28 this.add(stop); 29 30 toggle = new Button("Toggle"); 31 toggle.addActionListener(this); 32 this.add(toggle); 33 34 this.theCounter1 = new Counter(countField1, 0); 35 this.theCounter1.setPriority(Thread.MAX_PRIORITY); 36 this.theCounter2 = new Counter(countField2, 200); 37 this.theCounter2.setPriority(Thread.MIN_PRIORITY); 38 } 39 40 public void actionPerformed(ActionEvent e) 41 { 42 if (e.getSource().equals(start)) 43 { 44 this.theCounter1.start(); 45 this.theCounter2.start(); 46 } 47 else if (e.getSource().equals(stop)) 48 { 49 this.theCounter1.stopCounter(); 50 this.theCounter2.stopCounter(); 51 } 52 else if (e.getSource().equals(toggle)) 53 { 54 this.theCounter1.toggleCounter(); 55 this.theCounter2.toggleCounter(); 56 } 57 } 58 } 59 60 61 class Counter extends Thread 62 { 63 private int count; 64 private boolean running = true, paused = false; 65 private TextField theField; 66 67 68 public Counter(TextField t, int startValue) 69 { 70 this.theField = t; 71 this.count = startValue; 72 } 73 74 public void run() 75 { 76 while (running) 77 { 78 theField.setText("Count: "+this.count++); 79 try 80 { 81 Thread.currentThread().sleep(1); 82 83 synchronized(this) { 84 while (paused) 85 wait(); 86 } 87 } 88 catch (InterruptedException e) 89 { 90 theField.setText("Interrupted!"); 91 this.stopCounter(); 92 } 93 } 94 } 95 96 public int getCount() { return count; } 97 98 public void stopCounter() { this.running = false; } 99 100 public synchronized void toggleCounter() 101 { 102 this.paused = !this.paused; 103 if (!this.paused) notify(); 104 } 105 } 106 107
This applet will run at different speeds on different machines. I did not want to give a delay of 0, i.e.
sleep(0)
as this would allocate no resources to the system and could lead to
unresponsive buttons and a serious CPU load on your PC - Try it if you like!
© 2006
Dr. Derek Molloy
(DCU).