CS 4773 Object Oriented Systems
Threads

Code for the programs can be found in
/usr/local/courses/cs4773/spring2000/examples/set4


Previous Topic: Layouts

Introduction
Thread States
Ping Pong Application
Using Join
The Ping Applet
Why Use a Thread?
Attempting to Paint
Fixing PingBad.java
Using an External Thread
Removing suspend and resume
The Fixed PingExternalApplet
The Fixed PingExternalThead
The PingPong Applet
An Object Oriented Version
A Version That Uses A Canvas
Summary of Applets

Next Topic: A Simple Animation



Introduction

There are two main ways to use threads in Java:

Thread States

When you declare a Thread variable with
Thread ping
the variable is initially null.

If an applet implements Runnable it must have a run method. Executing:
ping = new Thread(this)
creates a new thread which can execute the run method of the applet. The thread can be started with its start method:
ping.start();
At this point the thread is in its active state. It remains active until it is stopped or it completes execution of its run method.

An active thread can be suspended with its suspend method. This delays further execution until its resume method is called. It is not an error to suspend a thread which has not yet started or has completed execution.


Ping Pong Application

PingPong.java

This thread prints out a word a given number of times with a given delay. It also shows the number of active threads.

sleep throws the InterruptedException so it must catch it.

public class PingPong extends Thread {
   String word;                 // what word to print
   int delay;                   // how long to pause
   int count;                   // number of iterations

   PingPong(String what, int time, int number) {
      word = what;
      delay = time;
      count = number;
      setName(what);
   }

   public void run() {
      try {
         for(int i=0;i < count;i++) {
            System.out.println(i+": "+word+":"+activeCount());
            sleep(delay);    // wait until next time
         }
      }  catch (InterruptedException e) {
         return;                 // end this thread
      }  
   }   
}

PingPongTest1.java

This is an application that just starts two copies of PingPong.
class PingPongTest1{

  public static void main (String[] args){
     PingPong ping;
     PingPong pong;

     ping = new PingPong("ping", 2000, 10);
     pong = new PingPong("PONG", 5000, 3);
     ping.start();
     pong.start();
  }
}

Ping Pong Test1 Output

You can obtain the following by executing make run1.
0: ping:3
0: PONG:3
1: ping:3
2: ping:3
1: PONG:3
3: ping:3
4: ping:3
2: PONG:3
5: ping:3
6: ping:3
7: ping:3
8: ping:2
9: ping:2

Using join

Join suspends the caller until the thread has completed.

The following example waits for each thread to complete and then prints a message. It also has a method which shows the threads.

class PingPongTest2{

   public static void showThreads(String msg) {
      Thread[] tlist = new Thread[50];
      int count;
      count = Thread.enumerate(tlist);
      System.out.println(msg + " Number of threads: "+count);
      for (int i=0;i < count;i++)
        System.out.println("    "+i+": "+tlist[i]);
   }

  public static void main (String[] args){
     PingPong ping;
     PingPong pong;

     showThreads("Start of main");
     ping = new PingPong("ping", 2000, 10);
     showThreads("ping created");
     pong = new PingPong("PONG", 3000, 5);
     showThreads("pong created");
     ping.start();
     pong.start();
     try {pong.join();} catch(InterruptedException e) {}
     showThreads("pong joined");
     try {ping.join();} catch(InterruptedException e) {}
     showThreads("ping joined");
  }
}

Join Ping Pong Output

You can obtain the following by executing make run2.
Start of main Number of threads: 1
    0: Thread[main,5,main]
ping created Number of threads: 2
    0: Thread[main,5,main]
    1: Thread[ping,5,main]
pong created Number of threads: 3
    0: Thread[main,5,main]
    1: Thread[ping,5,main]
    2: Thread[PONG,5,main]
0: ping:3
0: PONG:3
1: ping:3
1: PONG:3
2: ping:3
2: PONG:3
3: ping:3
4: ping:3
3: PONG:3
5: ping:3
4: PONG:3
6: ping:3
7: ping:3
pong joined Number of threads: 2
    0: Thread[main,5,main]
    1: Thread[ping,5,main]
8: ping:2
9: ping:2
ping joined Number of threads: 1
    0: Thread[main,5,main]

The Ping Applet

This is a simple applet which displays the word ping 10 times at a rate of once per second. It also outputs a sound.

Pushing the Start button starts the thread which executes the run method of the applet. The thread writes into a TextArea and outputs a sound. Since the thread executes the run method it has access to all of the variables of the applet.

The thread should be suspended when the the browser leaves the page containing the applet (the stop method) and resumed when the page is revisited (the start method). Note the test for a null pointer.

The Start button is disabled when the thread is running.
It is enabled when the thread is done.

When the thread is done a new ping thread is created so that it can be started again.

/* < Applet code = Ping
width = 300 height = 300 >
< /Applet >
*/

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

public class Ping extends Applet implements ActionListener, Runnable {
   TextArea output;
   Button startButton;
   AudioClip pingClip;
   Thread ping;
   Color backColor = Color.lightGray;
   Color foreColor = Color.black;

   public void init() {
      setBackground(backColor);
      setForeground(foreColor);
      Panel p = new Panel();
      setLayout(new BorderLayout());
      output = new TextArea();
      output.setBackground(backColor);
      output.setForeground(foreColor);
      startButton = new Button("Start");
      startButton.setBackground(Color.red);
      add(BorderLayout.CENTER,output);
      add(BorderLayout.SOUTH,startButton);
      pingClip = getAudioClip(getCodeBase(),"ping.au");
      ping = new Thread(this);
      startButton.addActionListener(this);
   }

   public void stop() {
      if (ping != null)
         ping.suspend();
   }

   public void start() {
       if (ping != null)
          ping.resume();
   }

   public void run() {
      startButton.setEnabled(false);
      try {
         for(int i=0;i < 10;i++) {
            pingClip.play();
            output.append(i+": ping\n");
            Thread.sleep(1000);    // wait until next time
         }
      }  catch (InterruptedException e) {
         return;                 // end this thread
      }
      output.append("done\n");
      ping = new Thread(this);   // So thread can be started again
      startButton.setEnabled(true);
   }

   public void actionPerformed(ActionEvent e) {
      if (e.getSource() == startButton) {
         output.append("starting thread\n");
         ping.start();
      }
   }
}
Click Here to run this applet.

Why Use a Thread?

The example PingSimple.java below is almost identical to Ping.java but the Start button calls run directly rather than starting a new thread.
/* < Applet code = PingSimple
     width = 300 height = 300 >
   < /Applet >
*/

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

public class PingSimple extends Applet implements ActionListener {
   TextArea output;
   Button startButton;
   AudioClip pingClip;
   Color backColor = Color.lightGray;
   Color foreColor = Color.black;

   public void init() {
      setBackground(backColor);
      setForeground(foreColor);
      Panel p = new Panel();
      setLayout(new BorderLayout());
      output = new TextArea();
      output.setBackground(backColor);
      output.setForeground(foreColor);
      startButton = new Button("Start");
      startButton.setBackground(Color.red);
      add(BorderLayout.CENTER,output);
      add(BorderLayout.SOUTH,startButton);
      pingClip = getAudioClip(getCodeBase(),"ping.au");
      startButton.addActionListener(this);
   }

   public void run() {
      startButton.setEnabled(false);
      try {
         for(int i=0;i < 10;i++) {
            pingClip.play();
            output.append(i+": ping\n");
            Thread.sleep(1000);    // wait until next time
         }
      }  catch (InterruptedException e) {
         return;                 // end this thread
      }
      output.append("done\n");
      startButton.setEnabled(true);
   }

   public void actionPerformed(ActionEvent e) {
      if (e.getSource() == startButton) {
         output.append("starting thread\n");
         run();
      }
   }
}
It does behave a little differently in that the output continues if the browser leaves the page containing the applet.

Click Here to run this applet.


Attempting to Paint

We can see why a thread is necessary by trying to draw a string graphically rather than relying on a TextArea.

The applet PingBad.java attempts to update a count of the number of pings after each ping.

/* < Applet code = PingBad
     width = 300 height = 300 >
   < /Applet >
*/

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

public class PingBad extends Applet implements ActionListener {
   TextArea output;
   Button startButton;
   AudioClip pingClip;
   int count;
   Color backColor = Color.lightGray;
   Color foreColor = Color.black;

   public void init() {
      setBackground(backColor);
      setForeground(foreColor);
      Panel p = new Panel();
      setLayout(new BorderLayout());
      output = new TextArea();
      output.setBackground(Color.yellow);
      output.setForeground(foreColor);
      startButton = new Button("Start");
      startButton.setBackground(Color.red);
      add(BorderLayout.NORTH,output);
      add(BorderLayout.SOUTH,startButton);
      pingClip = getAudioClip(getCodeBase(),"ping.au");
      startButton.addActionListener(this);
      count = 0;
   }

   public void paint(Graphics g) {
      g.drawString("Ping Count is "+count,20,250);
   }

   public void run() {
      startButton.setEnabled(false);
      try {
         for(int i=0;i < 10;i++) {
            pingClip.play();
            output.append(i+": ping\n");
            count++;
            repaint();
            Thread.sleep(1000);    // wait until next time
         }
      }  catch (InterruptedException e) {
         return;                 // end this thread
      }
      repaint();
      output.append("done\n");
      startButton.setEnabled(true);
   }

   public void actionPerformed(ActionEvent e) {
      if (e.getSource() == startButton) {
         output.append("starting thread\n");
         run();
      }
   }
}
The count is only redisplayed when the loop is complete even though repaint() is called inside the loop.

Click Here to run this applet.


Fixing PingBad.java

The applet PingFixed.java is almost identical to Ping.java except that it starts a new thread to execute run.
/* < Applet code = PingFixed
     width = 300 height = 300 >
   < /Applet >
*/

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

public class PingFixed extends Applet implements ActionListener, Runnable {
   TextArea output;
   Button startButton;
   AudioClip pingClip;
   int count;
   Thread ping;
   Color backColor = Color.lightGray;
   Color foreColor = Color.black;

   public void init() {
      setBackground(backColor);
      setForeground(foreColor);
      Panel p = new Panel();
      setLayout(new BorderLayout());
      output = new TextArea();
      output.setBackground(Color.yellow);
      output.setForeground(foreColor);
      startButton = new Button("Start");
      startButton.setBackground(Color.red);
      add(BorderLayout.NORTH,output);
      add(BorderLayout.SOUTH,startButton);
      pingClip = getAudioClip(getCodeBase(),"ping.au");
      ping = new Thread(this);
      startButton.addActionListener(this);
      count = 0;
   }

   public void stop() {
      if (ping != null)
         ping.suspend();
   }

   public void start() {
       if (ping != null)
          ping.resume();
   }

   public void paint(Graphics g) {
      g.drawString("Ping Count is "+count,20,250);
   }

   public void run() {
      startButton.setEnabled(false);
      try {
         for(int i=0;i < 10;i++) {
            pingClip.play();
            output.append(i+": ping\n");
            count++;
            repaint();
            Thread.sleep(1000);    // wait until next time
         }
      }  catch (InterruptedException e) {
         return;                 // end this thread
      }
      repaint();
      output.append("done\n");
      ping = new Thread(this);   // So thread can be started again
      startButton.setEnabled(true);
   }

   public void actionPerformed(ActionEvent e) {
      if (e.getSource() == startButton) {
         output.append("starting thread\n");
         ping.start();
      }
   }
}
Click Here to run this applet.


Here are the differences between PingBad.java and PingFixed.java:
vip% diff PingBad.java PingFixed.java
1c1
< /* < Applet code = PingBad
---
>  /* < Applet code = PingFixed
10c10
<  public class PingBad extends Applet implements ActionListener {
---
>  public class PingFixed extends Applet implements ActionListener, Runnable {
14a15
>     Thread ping;
30a32
>        ping = new Thread(this);
34a37,46
>     public void stop() {
>        if (ping != null)
>           ping.suspend();
>     }
>  
>     public void start() {
>         if (ping != null)
>            ping.resume();
>     }
>  
53a66
>        ping = new Thread(this);   // So thread can be started again
60c73
<           run();
---
>           ping.start();

Using an External Thread

PingExternalThread.java extends Thread and contains the code from the run method of the applet.
In order for this to have access to the methods and variables of the applet, the applet is passed as a parameter.
public class PingExternalThread extends Thread {

   private PingExternalApplet ap;

   public PingExternalThread(PingExternalApplet ap) {
      this.ap = ap;
   }

   public void run() {
      ap.startButton.setEnabled(false);
      try {
         for(int i=0;i < 10;i++) {
            ap.pingClip.play();
            ap.output.append(i+": ping\n");
            ap.count++;
            ap.repaint();
            sleep(1000);    // wait until next time
         }
      }  catch (InterruptedException e) {
         return;                 // end this thread
      }
      ap.repaint();
      ap.output.append("done\n");
      ap.newThread();          // So thread can be started again
      ap.startButton.setEnabled(true);
   }
}
The applet PingExternalApplet.java uses this thread.
It is almost identical to PingFixed without the run method.
/* < Applet code = PingExternalApplet
     width = 300 height = 300 >
   < /Applet >
*/


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

public class PingExternalApplet extends Applet implements ActionListener {
   TextArea output;
   Button startButton;
   Button suspendButton;
   Button resumeButton;
   AudioClip pingClip;
   int count;
   PingExternalThread ping;
   Color backColor = Color.lightGray;
   Color foreColor = Color.black;

   public void init() {
      setBackground(backColor);
      setForeground(foreColor);
      setLayout(new BorderLayout());
      output = new TextArea();
      output.setBackground(Color.yellow);
      output.setForeground(foreColor);
      startButton = new Button("Start");
      startButton.setBackground(Color.red);
      suspendButton = new Button("Suspend");
      resumeButton = new Button("Resume");
      Panel p = new Panel();
      p.setLayout(new GridLayout(1,3));
      p.add(startButton);
      p.add(suspendButton);
      p.add(resumeButton);
      add(BorderLayout.NORTH,output);
      add(BorderLayout.SOUTH,p);
      pingClip = getAudioClip(getCodeBase(),"ping.au");
      newThread();
      startButton.addActionListener(this);
      suspendButton.addActionListener(this);
      resumeButton.addActionListener(this);
      count = 0;
   }

   void newThread() {
      ping = new PingExternalThread(this);
   }

   public void stop() {
      if (ping != null)
         ping.suspend();
   }

   public void start() {
       if (ping != null)
          ping.resume();
   }

   public void paint(Graphics g) {
      g.drawString("Ping Count is "+count,20,250);
   }

   public void actionPerformed(ActionEvent e) {
      if (e.getSource() == startButton) {
         output.append("starting thread\n");
         ping.start();
      }
      if (e.getSource() == suspendButton) {
         output.append("suspending thread\n");
         stop();
      }
      if (e.getSource() == resumeButton) {
         output.append("resuming thread\n");
         start();
      }
   }
}
Click Here to run this applet.

What do you think would happen if you pushed Suspend three times and then pushed Resume?


Removing the suspend and resume

A funny thing happened on the way from Java 1.1 to Java 1.2.

Thread.suspend and Thread.resume were deprecated.

This means that it is suggested that you do not use them and that they may be removed in a later version of Java.

The problems with these methods are related to the way Java does synchronication and will be discussed later.

For now, I will just present you the correct way of doing this so that you have some examples of correct code.


The Fixed PingExternalApplet

The PingExternalAppletSuspend.java applet calls the setSuspend method of the thread with a true argument to suspend it and a false argument to resume it.
/* < Applet code = PingExternalAppletSuspend
     width = 300 height = 300 >
   < /Applet >
*/

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

public class PingExternalAppletSuspend extends Applet implements ActionListener {
   TextArea output;
   Button startButton;
   Button suspendButton;
   Button resumeButton;
   AudioClip pingClip;
   int count;
   PingExternalThreadSuspend ping;
   Color backColor = Color.lightGray;
   Color foreColor = Color.black;

   public void init() {
      setBackground(backColor);
      setForeground(foreColor);
      setLayout(new BorderLayout());
      output = new TextArea();
      output.setBackground(Color.yellow);
      output.setForeground(foreColor);
      startButton = new Button("Start");
      startButton.setBackground(Color.red);
      suspendButton = new Button("Suspend");
      resumeButton = new Button("Resume");
      Panel p = new Panel();
      p.setLayout(new GridLayout(1,3));
      p.add(startButton);
      p.add(suspendButton);
      p.add(resumeButton);
      add(BorderLayout.NORTH,output);
      add(BorderLayout.SOUTH,p);
      pingClip = getAudioClip(getCodeBase(),"ping.au");
      newThread();
      startButton.addActionListener(this);
      suspendButton.addActionListener(this);
      resumeButton.addActionListener(this);
      count = 0;
   }

   void newThread() {
      ping = new PingExternalThreadSuspend(this);
   }

   public void stop() {
      if (ping != null)
         ping.setSuspend(true);
   }

   public void start() {
       if (ping != null)
          ping.setSuspend(false);
   }

   public void paint(Graphics g) {
      g.drawString("Ping Count is "+count,20,250);
   }

   public void actionPerformed(ActionEvent e) {
      if (e.getSource() == startButton) {
         output.append("starting thread\n");
         ping.start();
      }
      if (e.getSource() == suspendButton) {
         output.append("suspending thread\n");
         stop();
      }
      if (e.getSource() == resumeButton) {
         output.append("resuming thread\n");
         start();
      }
   }
}

The Fixed PingEnternalThread

The PingExternalThreadSuspend.java thread hands the suspend correctly. Instead of calling suspend you use the method setSuspend(boolean f). When called with the argument true it sets the local flag suspendFlag to true. The run method checks to see if it needs to be suspended in the loop after it wakes up from the sleep by calling the checkSuspend method. This method checks the flag, and if true, put the thread to sleep by calling wait, which must by called inside a synchronized method.

If setSuspend is called with parameter false it will wake up the thread using notify().

public class PingExternalThreadSuspend extends Thread {

   private PingExternalAppletSuspend ap;
   private boolean suspendFlag;

   public PingExternalThreadSuspend(PingExternalAppletSuspend ap) {
      this.ap = ap;
   }

   public void run() {
      ap.startButton.setEnabled(false);
      try {
         for(int i=0;i < 10;i++) {
            ap.pingClip.play();
            ap.output.append(i+": ping\n");
            ap.count++;
            ap.repaint();
            sleep(1000);    // wait until next time
            checkSuspend();
         }
      }  catch (InterruptedException e) {
         return;                 // end this thread
      }
      ap.repaint();
      ap.output.append("done\n");
      ap.newThread();          // So thread can be started again
      ap.startButton.setEnabled(true);
   }

   synchronized private void checkSuspend() {
      try {
       while (suspendFlag) 
           wait();
      }  catch (InterruptedException e) {
         return;
      }
   }

   synchronized public void setSuspend(boolean f) {
      suspendFlag = f;
      if (!suspendFlag) notify();
   }
}

Click Here to run this applet.


The PingPong Applet

If you want to run two threads it is inconvenient to use the run method of the applet for both since you cannot pass parameters to run.

Here we use an external thread similar to PingExternalThread, but we pass the TextAreas and a clip rather than passing the applet.

The PingPongForApplet

The PingPongForApplet.java thread writes strings to two text areas and outputs a sound. The string contains a count of the active threads.

import java.awt.*;
import java.applet.*;

public class PingPongForApplet extends Thread {
   String word;                 // what word to print
   int delay;                   // how long to pause
   int count;                   // number of iterations
   TextArea area1;
   TextArea area2;
   AudioClip clip;
   private boolean suspendFlag = false;

   PingPongForApplet(String what, int time, int number, AudioClip clip,
      TextArea area1, TextArea area2) {
      word = what;
      delay = time;
      count = number;
      this.area1 = area1;
      this.area2 = area2;
      this.clip = clip;
      try {
         setName(what);
      }  
      catch (SecurityException e) {
         System.out.println("Warning: could not set the name of thread "+what);
      }  
   }

   public void run() {
      try {
         for(int i=0;i < count;i++) {
            clip.play();
            area1.append(i+": "+word+":"+activeCount()+"\n");
            area2.append(i+": "+word+":"+activeCount()+"\n");
            sleep(delay);    // wait until next time
            checkSuspend();
         }
      }  catch (InterruptedException e) {
         return;                 // end this thread
      }
      area1.append(word+" done\n");
      area2.append(word+" done\n");
   }

   synchronized private void checkSuspend() {
      try {
       while (suspendFlag)
           wait();
      }  catch (InterruptedException e) {
         return;
      }  
   }   

   synchronized public void setSuspend(boolean f) {
      suspendFlag = f;
      if (!suspendFlag) notify();
   }
}
Here is the PPApplet.java applet which starts two of these threads.

Instead of disabling the Start button until the threads are done, it changes it to a Stop button which suspends (using the new method rather than by calling suspend the threads.

The threads are not automatically resumed if the browser reenters the page containing the applet, the Start button must be pushed first.

/* < Applet code = PPApplet
     width = 600 height = 400 >
   < /Applet >
*/

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

public class PPApplet extends Applet implements ActionListener {
   TextArea outputCommon;
   TextArea outputPing;
   TextArea outputPong;
   Button startButton;
   PingPongForApplet ping;
   PingPongForApplet pong;
   AudioClip pingClip;
   AudioClip pongClip;
   Color backColor = Color.lightGray;
   Color foreColor = Color.black;
   boolean useActive = true;

   public void init() {
      if (getParameter("inhibitactive") != null)
         useActive = false;
      setBackground(backColor);
      setForeground(foreColor);
      Panel q = new Panel();
      setLayout(new BorderLayout());
      q.setLayout(new GridLayout(1,3));
      outputCommon = new TextArea(18,18);
      outputPing = new TextArea(18,18);
      outputPong = new TextArea(18,18);
      outputCommon.setBackground(Color.yellow);
      outputPing.setBackground(Color.cyan);
      outputPong.setBackground(Color.cyan);
      outputCommon.setForeground(foreColor);
      outputPing.setForeground(foreColor);
      outputPong.setForeground(foreColor);
      q.add(outputPing);
      q.add(outputCommon);
      q.add(outputPong);
      add(BorderLayout.NORTH,q);
      startButton = new Button("Start");
      startButton.addActionListener(this);
      startButton.setBackground(Color.pink);
      add(BorderLayout.SOUTH,startButton);
      pingClip = getAudioClip(getCodeBase(),"ping.au");
      pongClip = getAudioClip(getCodeBase(),"pong.au");
   }

   public void paint(Graphics g) {
      g.drawString("Nothing Useful here.",10,350);
   }

   public void stop() {
      startButton.setLabel("Start");
      if (ping != null)
         ping.setSuspend(true);
      if (pong != null)
         pong.setSuspend(false);
   }

   private void startThreads() {
      if (ping == null) {
         ping = new PingPongForApplet("ping", 2000, 10, pingClip,
             outputPing,outputCommon);
         outputCommon.append("ping started\n");
         outputPing.append("ping started\n");
         ping.start();
      }
      else if (ping.isAlive()) {
         outputCommon.append("ping resumed\n");
         outputPing.append("ping resumed\n");
         ping.setSuspend(false);
      }
      else {
         ping = new PingPongForApplet("ping", 2000, 10, pingClip,
              outputPing,outputCommon);
         outputCommon.append("ping restarted\n");
         outputPing.append("ping restarted\n");
         ping.start();
      }
      if (pong == null) {
         pong = new PingPongForApplet("PONG", 3000, 5, pongClip,
             outputPong,outputCommon);
         outputCommon.append("pong started\n");
         outputPong.append("pong started\n");
         pong.start();
      }
      else if (pong.isAlive()) {
         outputCommon.append("pong resumed\n");
         outputPong.append("pong resumed\n");
         pong.setSuspend(false);
      }
      else {
         pong = new PingPongForApplet("PONG", 3000, 5, pongClip,
            outputPong,outputCommon);
         outputCommon.append("pong restarted\n");
         outputPong.append("pong restarted\n");
         pong.start();
      }
   }

   private String getActiveCount() {
      if (useActive)
         return ": "+Thread.activeCount();
      return "";
   }
 
   void suspendThreads() {
      outputPing.append("ping suspended\n");
      outputPong.append("PONG suspended\n");
      ping.setSuspend(true);
      pong.setSuspend(true);
   }

   public void actionPerformed(ActionEvent e) {
      if (e.getSource() == startButton) {
         if (useActive)
            outputCommon.append("Number of threads"+getActiveCount()+"\n");
         if (startButton.getLabel().equals("Start")) {
            startButton.setLabel("Stop");
            outputCommon.append("Start Button Pushed\n");
            startThreads();
         }
         else if (startButton.getLabel().equals("Stop")) {
            startButton.setLabel("Start");
            outputCommon.append("Stop Button Pushed\n");
            suspendThreads();
         }

      }
   }
}
The most complicated part of this is the startThreads method which attempts to start the two threads.

Click Here to run this applet.


An Object Oriented Version

One of the principles of object oriented programming is to make reusable objects.
The thread above has a very specific interface in which you pass two text areas and an audio clip.

Conceptually, the thread waits and does output. The type of output should be general.

We can instead have the caller provide a method to do the output. You can do this in a straight forward way by passing the caller as an argument as we did in PingExternalThread, but this requires having the thread depend on the class of the caller.

Instead we create an interface which describes the output.
This is the interface DisplayInfo.java.

public interface DisplayInfo {

   public void showString(int id, String str);
   
}
It has one method. Any class which implements this interface must define this method.

The PingPongThread.java is also very simple.

It is passed an id which can be used to identify the instance of the thread and an object of class DisplayInfo.
DisplayInfo acts as a prototype for its methods and variables so that these can be used by this class.
In this case there is only one method.

import java.awt.*;
import java.applet.*;

public class PingPongThread extends Thread {
   private int delay;            // how long to pause
   private int count;            // number of iterations
   private int id;               // an id for this thread
   private String what;          // String to display
   private DisplayInfo info;     // call showString to display
   private boolean suspendFlag;  // true if thread is to be suspended

   PingPongThread(int id, String what, int time, int number, DisplayInfo info) {
      this.id = id;
      delay = time;
      count = number;
      this.what = what;
      this.info = info;
      try {
         setName(what);
      }  
      catch (SecurityException e) {
         System.out.println("Warning: could not set the name of thread "+what);
      }  
   }

   public void run() {
      try {
         for(int i=0;i < count-1;i++) {
            info.showString(id,i+": "+what);
            sleep(delay);    // wait until next time
            checkSuspend();
         }
      }  catch (InterruptedException e) {
         return;                 // end this thread
      }
      info.showString(id,(count-1)+": "+what+"\n"+what+" done");
   }

   synchronized private void checkSuspend() {
      try {
       while (suspendFlag)
           wait();
      }  catch (InterruptedException e) {
         return;
      }  
   }   

   synchronized public void setSuspend(boolean f) {
      suspendFlag = f;
      if (!suspendFlag) notify();
   }
}
All of the structure of the output is contained in the applet PingPongApplet.java

The only interesting part is the showString method. It tests the ID of the calling thread and outputs accordingly. It also keeps a count of the number of times it is called with each ID (pingCount and pongCount) so that these values can be displayed by the paint method.

   public void showString(int id, String str) {
      if (id == PINGID) {
         pingCount++;
         pingClip.play();
         outputCommon.append(str+getActiveCount()+"\n");
         outputPing.append(str+getActiveCount()+"\n");
      }  
      else if (id == PONGID) {
         pongCount++;
         pongClip.play();
         outputCommon.append(str+getActiveCount()+"\n");
         outputPong.append(str+getActiveCount()+"\n");
      }  
      repaint();
   }
Here is the complete applet.
/* < Applet code = PingPongApplet
     width = 600 height = 400 >
   < /Applet >
*/

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

public class PingPongApplet extends Applet 
        implements ActionListener, DisplayInfo {
   final int PINGID = 0;
   final int PONGID = 1;
   TextArea outputCommon;
   TextArea outputPing;
   TextArea outputPong;
   Button startButton;
   PingPongThread ping;
   PingPongThread pong;
   AudioClip pingClip;
   AudioClip pongClip;
   int pingCount = 0;
   int pongCount = 0;
   Color backColor = Color.lightGray;
   Color foreColor = Color.black;
   Panel outputPanel;
   boolean useActive = true;

   public void init() {
      if (getParameter("inhibitactive") != null)
         useActive = false;
      setBackground(backColor);
      setForeground(foreColor);
      outputPanel = new Panel();
      setLayout(new BorderLayout());
      outputPanel.setLayout(new GridLayout(1,3));
      outputCommon = new TextArea(18,18);
      outputPing = new TextArea(18,18);
      outputPong = new TextArea(18,18);
      outputCommon.setBackground(Color.yellow);
      outputPing.setBackground(Color.cyan);
      outputPong.setBackground(Color.cyan);
      outputCommon.setForeground(foreColor);
      outputPing.setForeground(foreColor);
      outputPong.setForeground(foreColor);
      outputPanel.add(outputPing);
      outputPanel.add(outputCommon);
      outputPanel.add(outputPong);
      add(BorderLayout.NORTH,outputPanel);
      startButton = new Button("Start");
      startButton.addActionListener(this);
      startButton.setBackground(Color.pink);
      add(BorderLayout.SOUTH,startButton);
      pingClip = getAudioClip(getCodeBase(),"ping.au");
      pongClip = getAudioClip(getCodeBase(),"pong.au");
   }

   public void paint(Graphics g) {
      Insets insets;
      int starty;
      insets = getInsets();
      starty = insets.top + outputPanel.getBounds().height + 15;
      g.drawString("ping count = "+pingCount,10,starty);
      g.drawString("pong count = "+pongCount,10,starty + 15);
      if (useActive)
         g.drawString("thread count"+getActiveCount(),10,starty + 30);
   }

   public void stop() {
      startButton.setLabel("Start");
      if (ping != null)
         ping.setSuspend(true);
      if (pong != null)
         pong.setSuspend(true);
   }

   private void startThreads() {
      if (ping == null) {
         ping = new PingPongThread(PINGID,"ping", 2000, 10, this);
         outputCommon.append("ping started\n");
         outputPing.append("ping started\n");
         ping.start();
      }
      else if (ping.isAlive()) {
         outputCommon.append("ping resumed\n");
         outputPing.append("ping resumed\n");
         ping.setSuspend(false);
      }
      else {
         ping = new PingPongThread(PINGID,"ping", 2000, 10, this);
         outputCommon.append("ping restarted\n");
         outputPing.append("ping restarted\n");
         ping.start();
      }
      if (pong == null) {
         pong = new PingPongThread(PONGID,"PONG", 3000, 5, this);
         outputCommon.append("pong started\n");
         outputPong.append("pong started\n");
         pong.start();
      }
      else if (pong.isAlive()) {
         outputCommon.append("pong resumed\n");
         outputPong.append("pong resumed\n");
         pong.setSuspend(false);
      }
      else {
         pong = new PingPongThread(PONGID,"PONG", 3000, 5, this);
         outputCommon.append("pong restarted\n");
         outputPong.append("pong restarted\n");
         pong.start();
      }
   }

   void suspendThreads() {
      outputPing.append("ping suspended\n");
      outputPong.append("PONG suspended\n");
      ping.setSuspend(true);
      pong.setSuspend(true);
   }

   private String getActiveCount() {
      if (useActive)
         return ": "+Thread.activeCount();
      return "";
   }

   public void showString(int id, String str) {
      if (id == PINGID) {
         pingCount++;
         pingClip.play();
         outputCommon.append(str+getActiveCount()+"\n");
         outputPing.append(str+getActiveCount()+"\n");
      }
      else if (id == PONGID) {
         pongCount++;
         pongClip.play();
         outputCommon.append(str+getActiveCount()+"\n");
         outputPong.append(str+getActiveCount()+"\n");
      }
      repaint();
   }

   public void actionPerformed(ActionEvent e) {
      if (e.getSource() == startButton) {
         if (useActive)
            outputCommon.append("Number of threads"+getActiveCount()+"\n");
         if (startButton.getLabel().equals("Start")) {
            startButton.setLabel("Stop");
            outputCommon.append("Start Button Pushed\n");
            startThreads();
         }
         else if (startButton.getLabel().equals("Stop")) {
            startButton.setLabel("Start");
            outputCommon.append("Stop Button Pushed\n");
            suspendThreads();
         }

      }
   }
}
Click Here to run this applet.


A Version That Uses A Canvas

The applet PingPongAppletCanvas uses the same thread but paints the information in a canvas rather than directly in the applet.
/* < Applet code = PingPongAppletCanvas
     width = 600 height = 400 >
   < /Applet >
*/

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

class MyCanvas extends Canvas {
   private Image buffer;

   void setBuffer(Image buffer) {
      this.buffer = buffer;
   }

   public void paint(Graphics g) {
      if (buffer == null) return;
      g.drawImage(buffer,0,0,null);
   }
}

public class PingPongAppletCanvas extends Applet 
        implements ActionListener, DisplayInfo {
   final int PINGID = 0;
   final int PONGID = 1;
   TextArea outputCommon;
   TextArea outputPing;
   TextArea outputPong;
   Button startButton;
   PingPongThread ping;
   PingPongThread pong;
   AudioClip pingClip;
   AudioClip pongClip;
   int pingCount = 0;
   int pongCount = 0;
   Color backColor = Color.lightGray;
   Color foreColor = Color.black;
   MyCanvas mc;
   Image buffer = null;
   Graphics gc;
   int imageWidth = 0;
   int imageHeight = 0;
   int currentWidth = 0;
   int currentHeight = 0;
   boolean useActive = true;

   public void init() {
      if (getParameter("inhibitactive") != null)
         useActive = false;
      setBackground(backColor);
      setForeground(foreColor);
      Panel outputPanel = new Panel();
      setLayout(new BorderLayout());
      outputPanel.setLayout(new GridLayout(1,3));
      outputCommon = new TextArea(18,18);
      outputPing = new TextArea(18,18);
      outputPong = new TextArea(18,18);
      outputCommon.setBackground(Color.yellow);
      outputPing.setBackground(Color.cyan);
      outputPong.setBackground(Color.cyan);
      outputCommon.setForeground(foreColor);
      outputPing.setForeground(foreColor);
      outputPong.setForeground(foreColor);
      outputPanel.add(outputPing);
      outputPanel.add(outputCommon);
      outputPanel.add(outputPong);
      add(BorderLayout.NORTH,outputPanel);
      startButton = new Button("Start");
      startButton.addActionListener(this);
      startButton.setBackground(Color.pink);
      add(BorderLayout.SOUTH,startButton);
      mc = new MyCanvas();
      add(BorderLayout.CENTER,mc);
      pingClip = getAudioClip(getCodeBase(),"ping.au");
      pongClip = getAudioClip(getCodeBase(),"pong.au");
   }

   private void setupImages() {
      if ( (buffer != null) && (getBounds().width == currentWidth) &&
           (getBounds().height == currentHeight) ) return;
      imageWidth = mc.getBounds().width;
      imageHeight = mc.getBounds().height;
      if ( (imageWidth <= 0) || (imageHeight <= 0) ) return;
      buffer = createImage(imageWidth,imageHeight);
      mc.setBuffer(buffer);
      currentWidth = getBounds().width;
      currentHeight = getBounds().height;
      gc = buffer.getGraphics();
   }

   private void fillBuffer() {
      gc.setColor(Color.green);
      gc.fillRect(0,0,imageWidth,imageHeight);
      gc.setColor(Color.black);
      gc.drawString("ping count = "+pingCount,10,20);
      gc.drawString("pong count = "+pongCount,10,35);
      if (useActive)
         gc.drawString("thread count"+getActiveCount(),10,50);
   }

   public void paint(Graphics g) {
      setupImages();
      fillBuffer();
      mc.repaint();
   }

   public void start() {
      repaint();
   }

   public void stop() {
      startButton.setLabel("Start");
      if (ping != null)
         ping.setSuspend(true);
      if (pong != null)
         pong.setSuspend(true);
   }

   private void startThreads() {
      if (ping == null) {
         ping = new PingPongThread(PINGID,"ping", 2000, 10, this);
         outputCommon.append("ping started\n");
         outputPing.append("ping started\n");
         ping.start();
      }
      else if (ping.isAlive()) {
         outputCommon.append("ping resumed\n");
         outputPing.append("ping resumed\n");
         ping.setSuspend(false);
      }
      else {
         ping = new PingPongThread(PINGID,"ping", 2000, 10, this);
         outputCommon.append("ping restarted\n");
         outputPing.append("ping restarted\n");
         ping.start();
      }
      if (pong == null) {
         pong = new PingPongThread(PONGID,"PONG", 3000, 5, this);
         outputCommon.append("pong started\n");
         outputPong.append("pong started\n");
         pong.start();
      }
      else if (pong.isAlive()) {
         outputCommon.append("pong resumed\n");
         outputPong.append("pong resumed\n");
         pong.setSuspend(false);
      }
      else {
         pong = new PingPongThread(PONGID,"PONG", 3000, 5, this);
         outputCommon.append("pong restarted\n");
         outputPong.append("pong restarted\n");
         pong.start();
      }
   }

   void suspendThreads() {
      outputPing.append("ping suspended\n");
      outputPong.append("PONG suspended\n");
      ping.setSuspend(true);
      pong.setSuspend(true);
   }


   private String getActiveCount() {
      if (useActive)
         return ": "+Thread.activeCount();
      return "";
   }

   public void showString(int id, String str) {
      if (id == PINGID) {
         pingCount++;
         pingClip.play();
         outputCommon.append(str+getActiveCount()+"\n");
         outputPing.append(str+getActiveCount()+"\n");
      }
      else if (id == PONGID) {
         pongCount++;
         pongClip.play();
         outputCommon.append(str+getActiveCount()+"\n");
         outputPong.append(str+getActiveCount()+"\n");
      }
      repaint();
   }

   public void actionPerformed(ActionEvent e) {
      if (e.getSource() == startButton) {
         if (useActive)
         outputCommon.append("Number of threads"+getActiveCount()+"\n");
         if (startButton.getLabel().equals("Start")) {
            startButton.setLabel("Stop");
            outputCommon.append("Start Button Pushed\n");
            startThreads();
         }
         else if (startButton.getLabel().equals("Stop")) {
            startButton.setLabel("Start");
            outputCommon.append("Stop Button Pushed\n");
            suspendThreads();
         }

      }
   }
}

Summary of Applets


Next topic: A Simple Animation