8.2 More Threads


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 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 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 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).


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:


123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
/** The Multi-Thread Counter Example - Derek Molloy, Dublin City University
*
*/

package ee402;

import java.awt.*;
import java.awt.event.*;

class Counter extends Thread{
        private int count = 0, delay;
        private boolean running = true, paused = false;
        private TextField outputField;
        
        public Counter(TextField t, int delay, int startAt){
                this.outputField = t;
                this.delay = delay;
                this.count = startAt;
        }
        
        public void run(){
                while(running){
                        this.outputField.setText("Count: " + count++);
                        try{
                                Thread.sleep(this.delay);
                                synchronized(this){
                                        while (this.paused) wait();
                                }
                        }
                        catch (InterruptedException e){
                                System.out.println("Counter was Interrupted!");
                                this.running = false;
                        }
                }
        }
        
        public void stopCount() { this.running = false; }
        
        public void toggleCount() {                 
                synchronized(this){
                        this.paused = !this.paused;
                        if (!this.paused) this.notify();
                }
        }
}

@SuppressWarnings("serial")
public class CounterApplication1 extends Frame implements ActionListener{

        private Counter count1, count2; //two separate threads
        private TextField count1Field, count2Field;
        private Button start, stop, toggle;
        
        public CounterApplication1() {
                super("Multi-Thread Counter");
                
                this.count1Field = new TextField(10);
                this.count2Field = new TextField(10);
                this.count1 = new Counter(count1Field, 100, 0); // delay 100ms start at 0
                this.count2 = new Counter(count2Field, 200, 50); // delay 200ms start at 50
                this.start = new Button("Start");
                this.stop = new Button("Stop");
                this.toggle = new Button("Toggle");
                
                this.start.addActionListener(this);
                this.stop.addActionListener(this);
                this.toggle.addActionListener(this);
                
                this.setLayout(new FlowLayout());
                this.add(this.count1Field);
                this.add(this.count2Field);
                this.add(this.start);
                this.add(this.stop);
                this.add(this.toggle);
        
                this.pack();
                this.setVisible(true);
        }

        public void actionPerformed(ActionEvent e) {
                if (e.getSource().equals(start)){
                        this.count1.start();
                        this.count2.start();
                }
                else if (e.getSource().equals(stop)){
                        this.count1.stopCount();
                        this.count2.stopCount();
                }
                else if (e.getSource().equals(toggle)){
                        this.count1.toggleCount();
                        this.count2.toggleCount();
                }
        }
        
        public static void main(String[] args) {
                new CounterApplication1();
        }
}


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?

  • The 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.

  • 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 stopCount() and toggleCount() 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 pausedrunning 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.

  • 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() 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 wait() and notify() methods. This is required by the language, and ensures that wait() and 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.

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, and 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.

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:

  • Add a new button called Up/Down that when pressed causes the two threaded counters to count the opposite way.

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.


Scheduling of Threads

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.

Thread Priorities

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):




4748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
...

@SuppressWarnings("serial")
public class CounterApplication2 extends Frame implements ActionListener{

        private Counter2 count1, count2; //two separate threads
        private TextField count1Field, count2Field;
        private Button start, stop, toggle;
        
        public CounterApplication2() {
                super("Multi-Thread Counter");
                
                this.count1Field = new TextField(10);
                this.count2Field = new TextField(10);
                this.count1 = new Counter2(count1Field, 1, 0); // delay 1ms start at 0
                this.count1.setPriority(Thread.MAX_PRIORITY);
                this.count2 = new Counter2(count2Field, 1, 50); // delay 1ms start at 50
                this.count2.setPriority(Thread.MIN_PRIORITY);
                this.start = new Button("Start");
                this.stop = new Button("Stop");
                this.toggle = new Button("Toggle");
                
                this.start.addActionListener(this);
                this.stop.addActionListener(this);
                this.toggle.addActionListener(this);
                
                this.setLayout(new FlowLayout());
                this.add(this.count1Field);
                this.add(this.count2Field);
                this.add(this.start);
                this.add(this.stop);
                this.add(this.toggle);
        
                this.pack();
                this.setVisible(true);
        }

        public void actionPerformed(ActionEvent e) {
                if (e.getSource().equals(start)){
                        this.count1.start();
                        this.count2.start();
                }
                else if (e.getSource().equals(stop)){
                        this.count1.stopCount();
                        this.count2.stopCount();
                }
                else if (e.getSource().equals(toggle)){
                        this.count1.toggleCount();
                        this.count2.toggleCount();
                }
        }
        
        public static void main(String[] args) {
                new CounterApplication2();
        }
}


These notes are copyright Dr. Derek Molloy, School of Electronic Engineering, Dublin City University, Ireland 2013-present. Please contact him directly before reproducing any of the content in any way.
Comments