Threads - More Aspects

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

Figure 11.3. A Suspend/Resume Multi-Counter Example

A Suspend/Resume Multi-Counter Example

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:

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:

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

Figure 11.4. My Up/Down Counter Solution

My Up/Down Counter Solution

Solution: The solution is here - UpDownCounterApplet.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 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.

Figure 11.5. Priority Counter Example

Priority Counter Example

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!